Skip to Content
Course content

218: Designing a Flexible Content Model

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 type and a content (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, and cta_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.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.