The Goal: Connecting All the MVC Pieces

This is the final and most rewarding lesson of this module. We will connect our Model, View, and Controller to dynamically display a single blog post from the database based on the slug in the URL. Here's the complete user flow:

  1. A user visits /post/your-post-slug.
  2. Our Router matches the URL and calls the show() method on the PostController, passing "your-post-slug" as an argument.
  3. The Controller creates an instance of the Post Model.
  4. The Controller asks the Model to find the post with that specific slug.
  5. The Model queries the database and returns the post data (or nothing if not found).
  6. The Controller receives the data from the Model and passes it to a View file.
  7. The View renders the HTML, displaying the post's title, content, and other details.

Step 1: Updating the Post Model

Our Post model currently only has a method to fetch all posts. We need to add a new method to find a single post by its slug. We'll also join with the users table to get the author's name.

Update your file: app/Models/Post.php


<?php

namespace App\Models;

use App\Core\Model;

class Post extends Model
{
    /**
     * Fetch all posts from the database.
     */
    public function fetchAll()
    {
        $stmt = $this->pdo->query("SELECT title, content FROM posts ORDER BY created_at DESC LIMIT 5");
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
    }

    /**
     * Find a single post by its slug.
     *
     * @param string $slug The post slug.
     * @return mixed The post data as an array, or false if not found.
     */
    public function findBySlug($slug)
    {
        $sql = "SELECT p.*, u.username AS author_name
                FROM posts p
                JOIN users u ON p.user_id = u.id
                WHERE p.slug = ?
                LIMIT 1";
        
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$slug]);
        return $stmt->fetch(\PDO::FETCH_ASSOC);
    }
}

Step 2: Creating the Single Post View

Next, let's create the HTML template for displaying a single post. For better organization, we'll create a new subdirectory inside views.

Create a new folder: views/post/

Create a new file inside that folder: views/post/single.view.php


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- The title will be dynamic based on the post -->
    <title><?php echo htmlspecialchars($post['title']); ?></title>
    <style>
        body { font-family: sans-serif; background-color: #f0f2f5; margin: 0; padding: 20px; color: #333; line-height: 1.6; }
        .container { max-width: 800px; margin: 40px auto; background: white; padding: 40px; border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); }
        h1 { font-size: 2.5rem; margin-bottom: 0; }
        .meta { color: #888; margin-bottom: 20px; border-bottom: 1px solid #eee; padding-bottom: 20px; }
        .content { margin-top: 20px; }
        .content p { margin-bottom: 1em; }
        .back-link { display: inline-block; margin-top: 30px; color: #007bff; text-decoration: none; }
    </style>
</head>
<body>
    <div class="container">
        <h1><?php echo htmlspecialchars($post['title']); ?></h1>
        <div class="meta">
            By <strong><?php echo htmlspecialchars($post['author_name']); ?></strong> on <?php echo date('F d, Y', strtotime($post['created_at'])); ?>
        </div>
        <div class="content">
            <?php echo $post['content']; // Assuming content is safe HTML from your editor ?>
        </div>

        <a href="/" class="back-link">&larr; Back to all posts</a>
    </div>
</body>
</html>

Step 3: Updating the PostController

Finally, let's update the PostController to connect the Model and the View. It will use the findBySlug method and render our new single.view.php file.

Update your file: app/Controllers/PostController.php


<?php

namespace App\Controllers;

use App\Models\Post;

class PostController
{
    public function show($slug)
    {
        // 1. Instantiate the model
        $postModel = new Post();

        // 2. Find the post by its slug
        $post = $postModel->findBySlug($slug);

        // 3. If no post is found, show a 404 error
        if (!$post) {
            http_response_code(404);
            echo "404 - Post not found.";
            return;
        }

        // 4. If found, render the view and pass the post data to it
        // Note the dot notation 'post.single' which our helper translates to 'post/single.view.php'
        view('post.single', [
            'post' => $post
        ]);
    }
}

Your Mission

  1. Update your app/Models/Post.php file with the new findBySlug method.
  2. Create the new directory views/post.
  3. Inside that new directory, create the file single.view.php.
  4. Update your app/Controllers/PostController.php file.
  5. Test it! Find a real post slug from your database and navigate to that URL in your browser (e.g., /post/your-actual-post-slug). You should see your full blog post rendered beautifully by your very own framework!

Module Complete!

Congratulations! You have successfully built a working, extensible MVC framework from the ground up. You've connected all the pieces: a front controller, a dynamic router, controllers, models that talk to the database, and views that render dynamic data. This is a huge accomplishment and provides the foundation for building almost any kind of web application.