Structuring Web Applications in PHP: MVC and Front Controller

Faizan Aalam
9 min readFeb 17, 2025

--

When building a web application, it’s essential to structure your project for scalability, maintainability, and ease of collaboration with other developers. Instead of writing extensive documentation to explain your approach, you can use proven architectural patterns like MVC (Model-View-Controller) and Repository. These patterns provide a clear structure, reduce confusion, and improve collaboration.

PHP, a language designed for web development, benefits significantly from these design patterns, making applications more powerful, efficient, and easier to manage.

What Are Design Patterns?

For those unfamiliar, a design pattern is a general, repeatable solution to a commonly occurring problem in software design. Architectural design patterns, such as MVC, define broader structures for organizing code in large-scale applications. MVC is one of the most widely used patterns in PHP development.

Creating a Simple Project in PHP

To begin, we’ll create a basic PHP project that prints “Hello, World!” and run it using PHP’s built-in server.

📂 Project Structure

simple-blog/
│── index.php

📜 Source Code for index.php

<?php

echo “Hello, World!”;

You can run this project using PHP’s built-in server by running the following command in your terminal:

php -S localhost:8000

Then, open your browser and go to:

http://localhost:8000

You should see “Hello, World!” displayed on the page. 🚀

Making Our Simple Blog More Functional

Now that we have a working project, let’s start building a basic blog website. Instead of just printing “Hello, World!”, we’ll update index.php to display blog posts dynamically.

Updating index.php to Display Blog Posts

We’ll use a dummy array to store blog posts, each containing:

  • Title → The title of the post.
  • Content → The main content of the post.
  • Tags → Comma-separated tags related to the post.
  • Author → The name of the post’s author.

Then, we’ll display them dynamically in index.php.

📜 Updated index.php Code

<?php

// Dummy array of blog posts
$posts = [
[
"title" => "Introduction to PHP",
"content" => "PHP is a popular scripting language for web development...",
"tags" => "PHP, Web Development",
"author" => "John Doe"
],
[
"title" => "Understanding MVC in PHP",
"content" => "MVC is a design pattern that separates an application into three parts...",
"tags" => "PHP, MVC, Design Patterns",
"author" => "Jane Smith"
],
[
"title" => "Why Use Design Patterns?",
"content" => "Design patterns help developers solve common software problems efficiently...",
"tags" => "PHP, Design Patterns",
"author" => "Mike Johnson"
]
];

?>

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Blog</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.post { border-bottom: 1px solid #ddd; padding: 20px 0; }
.tags { font-size: 14px; color: gray; }
</style>
</head>
<body>

<h1>My Simple Blog</h1>

<?php foreach ($posts as $post): ?>
<div class="post">
<h2><?= htmlspecialchars($post['title']); ?></h2>
<p><?= htmlspecialchars($post['content']); ?></p>
<p class="tags"><strong>Tags:</strong> <?= htmlspecialchars($post['tags']); ?></p>
<p><em>By <?= htmlspecialchars($post['author']); ?></em></p>
</div>
<?php endforeach; ?>

</body>
</html>

🔍 What This Code Does:
✔ Stores blog posts in a dummy array with title, content, tags, and author.
✔ Loops through the array using foreach to display each blog post dynamically.
✔ Uses htmlspecialchars() to prevent HTML injection.
✔ Displays tags directly from the array.
✔ Adds basic styling for better readability.

Scaling the Project: Moving to MVC

This structure works fine for a small project, but as it grows, it will become difficult to maintain and scale. A single index.php file containing both logic and presentation will become messy as more features are added.

This is where the MVC (Model-View-Controller) pattern comes in.

Understanding the MVC Pattern

Model-View-Controller (MVC) is a widely used architectural pattern that separates an application into three main components, each with a distinct role:

1️⃣ Model (Handles Data & Logic)

  • Manages the data and business logic of the application.
  • Handles database interactions and data processing.
  • Acts as a bridge between the Controller and the Database.

2️⃣ View (Handles Presentation)

  • Responsible for displaying data to the user.
  • Defines the User Interface (UI) and how information is presented.
  • No business logic should be present here.

3️⃣ Controller (Handles Application Flow)

  • Acts as an intermediary between the Model and View.
  • Processes user input, communicates with the Model and selects the View to render.
  • Controls the flow of data between components.

Benefits of Using MVC

Improves Code Organization → Separates logic, presentation, and user interactions.
Enhances Scalability → Makes it easier to add new features.
Promotes Code Reusability → Components can be reused across the application.
Simplifies Debugging & Maintenance → Each component has a specific role, making debugging easier.

Converting Our Simple Blog to MVC

Here’s what our new MVC project structure will look like:

simple-blog/
├── app/
│ ├── Controllers/
│ │ ├── PostController.php # Handles post logic
│ │
│ ├── Models/
│ │ ├── Post.php # Handles post data
│ │
│ ├── Views/
│ ├── posts.php # Displays the posts

└── public/
├── assets/ # CSS, JS, images
└── index.php # Public entry point (redirects to main index.php)

🚨 Don’t focus too much on the folder structure yet — we’ll explore each part step by step.

🔹 Step 1: Creating the Post Model

We already know that the Model represents data and acts as a bridge between the controller and the data source.
Here’s our Post.php model:

<?php

class Post {
public $title;
public $content;
public $tags;
public $author;

public function __construct($title, $content, $tags, $author) {
$this->title = $title;
$this->content = $content;
$this->tags = $tags;
$this->author = $author;
}

public static function all() {
$postsArray = [
["title" => "First Blog Post", "content" => "This is the first blog post.", "tags" => "PHP, MVC", "author" => "John Doe"],
["title" => "Second Blog Post", "content" => "Another blog post here.", "tags" => "Web, PHP", "author" => "Jane Doe"]
];

// Convert each array item to a Post object
return array_map(function($post) {
return new Post($post["title"], $post["content"], $post["tags"], $post["author"]);
}, $postsArray);
}
}

💡 Explanation

Represents a single post (Title, Content, Tags, Author)
all() method returns an array of Post objects
Uses array_map() to convert an array into Post instances

Now that we have the Post model, we need a controller to handle the logic for retrieving and displaying blog posts.

We already know that the controller acts as the middleman between the model (data) and the view (presentation). It retrieves data from the model and sends it to the view.

🔹 Step 2: Creating the Post Controller

Inside the app/Controllers directory, let's create a new file named PostController.php.

<?php

require_once __DIR__ . '/../Models/Post.php';

class PostController {
public function index() {
// Get all posts from the model
$posts = Post::all();
}
}

That’s the code for the PostController, which retrieves data using the Post model.

Before proceeding with creating the view, let’s first verify that everything is working correctly.

To run the application, use the following command:

php -S localhost:8000 -t public

This will start a local PHP server and ensure the application runs from the public/ directory instead of the project's root.

Now, if you open localhost:8000 in your browser, you won't see anything yet because we haven't instantiated the controller.

Instantiating the Controller

To test if everything is working, let’s instantiate PostController inside index.php and call the index() method.

<?php

require_once __DIR__ . '/../app/Controllers/PostController.php';

$controller = new PostController();
$controller->index();

You can add var_dump($posts); inside PostController to check if data retrieval is working correctly.

<?php

require_once __DIR__ . '/../Models/Post.php';

class PostController {
public function index() {
// Get all posts from the model
$posts = Post::all();

// Debug output to check if posts are being retrieved correctly
var_dump($posts);
}
}

Once you run the application and visit localhost:8000, you should see the dumped data on the screen.

Now that we have confirmed that data retrieval is working, it’s time to create a view to display the blog posts properly. 🚀

🔹 Step 3: Creating the View

A view is responsible for presenting data to the user. Instead of dumping raw data, we’ll create a file named posts.php inside the Views directory.

This file will serve as a simple HTML template that dynamically displays blog posts.

📝 Creating posts.php

We will use a structured approach similar to our initial index.php, but with modifications to fit our MVC architecture:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Blog</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.post { border-bottom: 1px solid #ddd; padding: 20px 0; }
.tags { font-size: 14px; color: gray; }
</style>
</head>
<body>

<h1>My Simple Blog</h1>

<?php foreach ($posts as $post): ?>
<div class="post">
<h2><?= htmlspecialchars($post->title); ?></h2>
<p><?= nl2br(htmlspecialchars($post->content)); ?></p>
<p class="tags"><strong>Tags:</strong> <?= htmlspecialchars($post->tags); ?></p>
<p><em>By <?= htmlspecialchars($post->author); ?></em></p>
</div>
<?php endforeach; ?>

</body>
</html>

🔄 Updating the Controller to Use the View

Now that we’ve created our view (posts.php), we need to update our PostController so that it retrieves data from the model and passes it to the view:

<?php

require_once __DIR__ . '/../Models/Post.php';

class PostController {
public function index() {
// Get all posts from the model
$posts = Post::all();

// Load the view and pass posts data
require_once __DIR__ . '/../Views/posts.php';
}
}

🚀 Now, when you run your application and visit localhost:8000, your blog posts will be displayed properly! 🎉

🔴 Issues With Our Current Approach

1️⃣ Single Entry Point Limitation
We currently have only index.php as our entry point.
If we need additional pages (e.g., viewing a single post), we must manually create new files in the public/ directory.

2️⃣ No Proper Routing System
We cannot handle dynamic URLs like posts/1 for viewing a single post.
Adding new pages requires manually creating new files, making maintenance more difficult.

These issues can be resolved by introducing a routing system capable of handling different paths and HTTP methods, such as GET and POST.

Choosing the Best Routing Pattern

When building a PHP application from scratch, the best design pattern for a router depends on scalability, maintainability, and flexibility. A Front Controller Pattern is a great starting point, as it ensures that all requests are handled through a single entry point, making the application more modular and easier to manage.

Implementing the Front Controller

What is the Front Controller?

The Front Controller is a design pattern where a single entry point (index.php) handles all incoming requests. Instead of creating multiple PHP files for different pages, one file directs all traffic to the appropriate controller.

How It Works

1️⃣ Single Entry Point:

2️⃣ Centralized Routing:

  • The router extracts the URL and determines which controller and method to call.
  • Example: /posts/1 would be routed to PostController@show.

3️⃣ Better Maintainability & Scalability:

  • No need to create separate PHP files for each new page.
  • The app remains clean and modular.

Building a Simple Router

Before jumping directly into the Front Controller, let’s first create a Router class that can:
✅ Handle GET and POST requests.
✅ Store routes and associate them with closures or controller methods.
Dispatch requests to the correct handler based on the URL.

📌 Create a Router.php file in app/Core directory

<?php

class Router {
private $routes = [];

// Register a GET route
public function get($path, $handler) {
$this->routes['GET'][$path] = $handler;
}

// Register a POST route
public function post($path, $handler) {
$this->routes['POST'][$path] = $handler;
}

// Dispatch the request to the correct handler
public function dispatch($method, $uri) {
$method = strtoupper($method);
$uri = strtok($uri, '?'); // Remove query parameters

if (isset($this->routes[$method][$uri])) {
$handler = $this->routes[$method][$uri];

// If the handler is a closure, call it directly
if (is_callable($handler)) {
return call_user_func($handler);
}

// If it's a controller method (e.g., "PostController@index")
if (is_string($handler)) {
list($controller, $method) = explode('@', $handler);

require_once __DIR__ . "/../Controllers/$controller.php";
$controllerInstance = new $controller();
return call_user_func([$controllerInstance, $method]);
}
}

// If no matching route is found, return a 404 response
http_response_code(404);
echo "404 - Page Not Found";
}
}

Defining the Front Controller

Now that we have our Router class set up, we need a Front Controller. This will serve as the single entry point for all incoming requests, handling routing centrally.

📍 Modify public/index.php

<?php

require_once __DIR__ . '/../app/Core/Router.php';

$router = new Router();

// Define application routes
$router->get('/', 'PostController@index');
$router->get('/posts', 'PostController@index');
$router->get('/posts/1', function () {
echo "Viewing Post 1";
});

// Dispatch the request
$router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);

Right now, public/index.php is acting as the Front Controller, ensuring all requests are processed in a structured manner.

Wrapping Up

With the MVC pattern and Front Controller in place, our application is now more structured, scalable, and maintainable. Instead of manually creating separate files for different pages, we now have a centralized entry point that efficiently handles all incoming requests. This approach keeps our codebase clean, organized, and better prepared for future enhancements.

However, this is just the beginning! While our current setup provides a solid foundation, there’s still room for improvement. We can introduce additional design patterns to make our router more flexible and efficient. Furthermore, integrating database handling will allow us to store and manage blog posts dynamically, eliminating hard-coded content.

In the next part of this series, we’ll take our application a step further by refining the routing system, optimizing request handling, and implementing a database layer to bring our simple blog to life. Stay tuned as we continue transforming this project into a fully functional MVC-based web application! 🚀

🔗 Source Code:

--

--

Faizan Aalam
Faizan Aalam

Written by Faizan Aalam

Freelancer | Programmer | Good Person

No responses yet