Structuring Web Applications in PHP: MVC and Front Controller
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:
- All requests go through
index.php
(e.g.,https://example.com/posts/1
). - This file processes the request and calls the appropriate controller.
2️⃣ Centralized Routing:
- The router extracts the URL and determines which controller and method to call.
- Example:
/posts/1
would be routed toPostController@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:
- Replit: Simple Blog
- GitHub: simple-blog-php