PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
218: Designing a Flexible Content Model
I was working on a project recently for a client who wanted a "simple" marketing site. In my head, that meant a few pages with a title, a body, and maybe a featured image. I set up a pages table in MySQL with exactly those columns. It felt clean. It felt professional. It was also a complete disaster the moment the client saw the first draft.
They didn't just want text; they wanted a "Hero Section" with a background video, followed by a "Three-Column Feature List," then a "Client Testimonial Slider," and finally a "Call to Action" banner. My rigid table was useless.
The Rigid Table Trap
Initially, my instinct was to just add more columns. I figured, "How hard can it be?" I added hero_title, hero_video_url, feature_1_text, feature_2_text, and so on. Let's look at how that looks in a basic PHP object:
class Page {
public $title;
public $content;
public $hero_title;
public $hero_video_url;
public $feature_1_text;
public $feature_2_text;
// ... this goes on forever
}
I tried to render this in the template, but I quickly hit a wall. What happens if the client wants four features instead of three? Do I add feature_4_text to the database? What if they want the Testimonial Slider before the Feature List? Since the data is tied to specific columns, the order is hard-coded into my PHP logic. I'm not building a website; I'm building a very expensive, very brittle spreadsheet.
Fighting the Schema
I realized I was treating "content" as a set of properties, when I should have been treating it as a sequence of components. I decided to scrap the wide table and try a one-to-many relationship. I created a page_blocks table.
The schema looked like this: id, page_id, block_type, and content. For the content field, I used a JSON column. This felt more flexible because I could throw any data I wanted into that JSON blob without changing the table structure.
Let's see how I tried to implement this in PHP:
$blocks = $db->query("SELECT * FROM page_blocks WHERE page_id = 1 ORDER BY sort_order ASC");
foreach ($blocks as $block) {
$data = json_decode($block['content'], true);
if ($block['block_type'] === 'hero') {
include 'templates/blocks/hero.php';
} elseif ($block['block_type'] === 'features') {
include 'templates/blocks/features.php';
}
// This is getting better, but this if/else chain is going to be a nightmare.
}
I observed that while the database was now flexible, my PHP code was becoming a giant switch statement. If I added a new block type, I had to hunt through my controllers to add another elseif. It was "flexible" in the database, but "rigid" in the code.
Moving Toward a Block-Based Architecture
To fix this, I needed to decouple the block type from the rendering logic. I decided to use a naming convention. Instead of a switch statement, I'd use the block_type to dynamically determine which template to load. This is a pattern I use constantly when I want to hand over a project to a junior dev or a freelancer—it makes it obvious where to add new things.
I refactored the loop to look like this:
foreach ($blocks as $block) {
$data = json_decode($block['content'], true);
$templatePath = "templates/blocks/{$block['block_type']}.php";
if (file_exists($templatePath)) {
// Extract $data so the template can use variable names like $title instead of $data['title']
extract($data);
include $templatePath;
} else {
echo "<!-- Template for {$block['block_type']} not found -->";
}
}
Now, the magic happens. If the client wants a "Pricing Table" block, I don't touch the database. I don't touch the main loop. I just create a file called templates/blocks/pricing_table.php and add the data to the JSON column in the DB. The system just "picks it up."
Connecting Data to Templates
The final tweak was ensuring my data structure was consistent. I found that if I didn't standardize the JSON keys, my templates were crashing. I started wrapping my block data in a simple Value Object to ensure defaults existed.
class ContentBlock {
public string $type;
public array $data;
public function __construct($type, $jsonContent) {
$this->type = $type;
$this->data = json_decode($jsonContent, true) ?? [];
}
public function get($key, $default = '') {
return $this->data[$key] ?? $default;
}
}
By shifting from "columns for every field" to "a sequence of typed blocks," I stopped fighting the database and started building a system that actually scales. You aren't just coding for the requirements you have today; you're coding for the inevitable "can we just add one more thing" request that comes next Tuesday.
📋 Practical Task
Implement a Dynamic Component Renderer
You are tasked with building a simplified version of the flexible content model discussed in the lesson. You don't need a real database for this exercise; you can use a hard-coded array to simulate the data.
- Create an array of "blocks" where each block is an associative array containing a
typeand acontent(which should be a JSON string). - Create at least three different template files (or simulated template logic) for the following types:
text_block,image_gallery, andcta_button. - Write a PHP loop that iterates through your blocks, decodes the JSON, and dynamically includes or renders the corresponding template based on the
type. - Ensure that if a block type is requested that doesn't have a corresponding template, the system displays a graceful fallback message rather than a fatal error.
There are no comments for now.