Implementing MVC pattern in PHP

Complete Guide to MVC in PHP: How it Works and How to Implement It


The MVC (Model-View-Controller) pattern is one of the most widely used approaches in web development, especially in PHP. In this article, we’ll go over how to implement MVC in a pure PHP project without using frameworks like Laravel or Symphony. We’ll break down each component of MVC and how they interact, while also exploring examples and best practices.

1. What is the MVC Pattern and Why is It Important in PHP?

1.1. Defining MVC: Model, View, Controller

MVC Pattern Diagram

The MVC pattern is a software architecture that organizes code into three main components: Model, View, and Controller. This separation keeps your code clean, modular, and easy to maintain.

  1. Model: Handles the business logic and data management. It interacts with the database and contains application rules.
  2. View: Responsible for presenting data to the user. This could be an HTML file, templates, or even JSON, depending on the application type.
  3. Controller: Acts as the mediator between the view and the model. It receives user requests, processes logic in the model, and returns the appropriate output through the view.

1.2. Benefits of Using MVC in PHP Projects

MVC offers several key benefits in PHP development:

  • Separation of concerns: Dividing the application into three components makes it easier to maintain and scale.
  • Code reusability: Reuse the same model or view across multiple controllers or actions, saving time and effort.
  • Modularity: Adds new features without impacting other system parts.

These features are especially useful in larger applications or long-term projects that need to scale.

2. Basic Structure of an MVC Project

A well-structured MVC project in PHP should have a clear folder separation for models, views, and controllers. A typical structure might look like this:

/php-mvc-example
|
|-- .htaccess  # URL config for Apache servers
|
|-- /public  # Browser-accessible public files
|   |-- /css
|   |   |-- global.css
|   |-- /js
|   |   |-- script.js
|   |-- /images
|   |-- index.php  # Entry point with Router implementation
|
|-- /src
|   |-- /Controllers
|   |   |-- UserController.php
|   |-- /Core
|   |   |-- config.php
|   |   |-- Controller.php
|   |   |-- Database.php
|   |-- /Models
|   |   |-- User.php
|   |-- /Routes
|   |   |-- Router.php
|   |   |-- routes.php
|   |-- /Templates
|   |   |-- header.php
|   |   |-- footer.php
|   |-- /Views
|       |-- /users
|           |-- create.php
|           |-- edit.php
|           |-- list.php
|
|-- /vendor  # Generated by Composer
|-- composer.json  # Composer dependencies and config
  1. public: Contains all assets accessible by the browser: CSS files, JavaScript, images, etc., plus index.php, which implements the router and serves as the application’s entry point.
  2. public/index.php: This is the main entry point. All requests are redirected here, and the router determines the controller and method based on the user’s request.
  3. src: Holds all source code. It’s organized to follow MVC, keeping the code clean and scalable.
  4. src/Core/: Essential classes and configurations for the project, such as base classes like Controller and the Database class with database connection settings.
  5. src/Controllers/: Contains all application controllers. These handle user requests, interact with the corresponding model, and return the appropriate view.
  6. src/Routes/: Holds the router and all application routes in routes.php.
  7. src/Models/: Contains models responsible for business logic and database interactions.
  8. src/Views/: Contains PHP files for the presentation layer, responsible for displaying data to the user.
  9. src/Templates/: Contains reusable pieces of PHP or HTML for multiple views.

3. Entry Point index.php

As mentioned, public/index.php implements the router, so every request passes through here, and the router communicates with controllers.

// php:public/index.php
<?php
require_once '../vendor/autoload.php';
require_once '../src/Core/config.php';
require_once '../src/Routes/routes.php';

This file simply includes the following:

  • autoload.php: For proper Composer functionality.
  • config.php: Configuration for global variables.
  • routes.php: Defines the routes as we’ll see later.

Ensure the server recognizes index.php as the entry point. Here, we use the Apache web server on XAMPP, so we’ve added the following .htaccess config file at the project root:

// apache:.htaccess
RewriteEngine On
RewriteBase /php-mvc-example/
RewriteCond %{THE_REQUEST} /public/([^\s?]*) [NC]
RewriteRule ^ %1 [L,NE,R=302]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ public/index.php [L]
RewriteRule ^((?!public/).*)$ public/$1 [L,NC]

4. Implementing the Router

The router analyzes the request URL and determines the appropriate controller and action to execute. It maps a URL to a controller and action and can capture additional URL parameters for the controller.

// php:src/Routes/Router.php
<?php
namespace App\Routes;

class Router
{
    protected $routes = [];

    public function add($method, $url, $action)
    {
        $this->routes[$method][$url] = $action;
    }

    public function handleRequest()
    {
        $requestUrl = $_SERVER['REQUEST_URI'];
        $requestMethod = $_SERVER['REQUEST_METHOD'];
        
        // Parse URL
        $url = explode('/', $requestUrl);
        $url = array_slice($url, 2);
        $requestUrl = '/' . implode('/', $url);
        
        $requestUrl = strtok($requestUrl, '?');

        if (isset($this->routes[$requestMethod][$requestUrl])) {
            list($controllerName, $methodName) = explode('@', $this->routes[$requestMethod][$requestUrl]);
            $controllerName = "App\\Controllers\\" . $controllerName;
            $controller = new $controllerName();
            return $controller->$methodName();
        }

        echo "Page not found.";
    }
}

With the router, you can use two main methods:

  • add: Adds routes based on request type (usually GET or POST).
  • handleRequest: Runs on each request and communicates with the controller through index.php. NOTE: Since we’re using XAMPP on Apache, we had to parse the URL. This may vary depending on your web server.

5. Core Elements of the Application

5.1. The Controller Class

Each controller extends from a base Controller class:

// php:src/core/Controller.php
<?php
namespace App\Core;

class Controller
{
    public function view($view, $data = [])
    {
        extract($data);
        require_once "../src/Views/$view.php";
    }

    public function model($model)
    {
        $modelClass = "App\\Models\\$model";
        return new $modelClass();
    }
}

Two essential methods for all controllers:

  • view: Renders a view file from src/Views/. You can also pass in data as an associative array (e.g., $data = ['users' => $user->getAll()]).
  • model: Similar to the view method but loads models from src/Models/.

5.2. The Database Class

Models often need to interact with a database, but it’s better to centralize database configuration in a separate class:

// php:src/Core/Database.php
<?php
namespace App\Core;
use PDO;
use PDOException;

class Database
{
    private $host = 'localhost';
    private $db_name = 'my_db';
    private $username = 'root';
    private $password = '';
    public $conn;

    public function connect()
    {
        $this->conn = null;
        try {
            $this->conn = new PDO('mysql:host

=' . $this->host . ';dbname=' . $this->db_name, $this->username, $this->password);
            $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } catch (PDOException $exception) {
            echo "Connection error: " . $exception->getMessage();
        }
        return $this->conn;
    }
}

Then, in each model, we will make the connect method execute prior to each query.

Remember, we’re structuring a very basic MVC framework to get our project started. In this case, $host, $db_name, $username, and $password should ideally be defined using environment variables to enhance security, but for development, we don’t need to overcomplicate things.

If you’re not using a MySQL database, you’ll need to change the connection URL in $this->conn = new PDO(DATABASE_URL);. For more information, you can refer to the PDO documentation.

5.3. The User Model

In this example, we’ll create a User model. Models in MVC handle data operations and represent business logic. This model interacts with the Database class to access the database:

// php:src/Models/User.php
<?php
namespace App\Models;
use App\Core\Database;
use PDO;

class User
{
    private $conn;
    private $table = 'users';

    public function __construct()
    {
        $database = new Database();
        $this->conn = $database->connect();
    }

    public function getAll()
    {
        $query = "SELECT * FROM $this->table";
        $stmt = $this->conn->prepare($query);
        $stmt->execute();
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    public function create($data)
    {
        $query = "INSERT INTO $this->table (name, email) VALUES (:name, :email)";
        $stmt = $this->conn->prepare($query);
        $stmt->bindParam(':name', $data['name']);
        $stmt->bindParam(':email', $data['email']);
        return $stmt->execute();
    }
}

The model offers two essential methods:

  • getAll: Retrieves all users from the users table.
  • create: Inserts a new user into the users table using provided data.

5.4. The UserController

The controller handles logic for user actions. It receives the user request, interacts with the model, and decides which view to show:

// php:src/Controllers/UserController.php
<?php
namespace App\Controllers;
use App\Core\Controller;
use App\Models\User;

class UserController extends Controller
{
    public function list()
    {
        $user = $this->model('User');
        $users = $user->getAll();
        return $this->view('users/list', ['users' => $users]);
    }

    public function create()
    {
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $user = $this->model('User');
            $user->create($_POST);
            header('Location: /users');
        }
        return $this->view('users/create');
    }
}

Two main actions in this controller:

  • list: Retrieves all users and sends them to the list view.
  • create: Handles user creation if the request method is POST, then redirects to the list of users.

5.5. Views (list.php and create.php)

Finally, views are responsible for presenting information. We’ll add a list.php file to show all users and a create.php file to create new users.

List View

// php:src/Views/users/list.php
<!DOCTYPE html>
<html>
<head>
    <title>User List</title>
</head>
<body>
    <h1>Users</h1>
    <a href="/users/create">Add New User</a>
    <ul>
        <?php foreach ($users as $user): ?>
            <li><?php echo $user['name'] . " (" . $user['email'] . ")"; ?></li>
        <?php endforeach; ?>
    </ul>
</body>
</html>

Create View

// php:src/Views/users/create.php
<!DOCTYPE html>
<html>
<head>
    <title>Create User</title>
</head>
<body>
    <h1>Create User</h1>
    <form method="POST" action="/users/create">
        <label>Name:</label>
        <input type="text" name="name" required>
        <label>Email:</label>
        <input type="email" name="email" required>
        <button type="submit">Save User</button>
    </form>
</body>
</html>

6. Configuring Routes

Finally, define application routes in routes.php. Each route links a URL path to a controller and action.

// php:src/Routes/routes.php
<?php
use App\Routes\Router;
use App\Controllers\UserController;

$router = new Router();

$router->add('GET', '/users', 'UserController@list');
$router->add('GET', '/users/create', 'UserController@create');
$router->add('POST', '/users/create', 'UserController@create');

$router->handleRequest();

In this setup:

  • GET /users maps to UserController@list to show all users.
  • GET /users/create maps to UserController@create to display the form.
  • POST /users/create maps to the same action for creating a new user.

7. Running the Project

  1. Database Setup: Make sure your database server is running and a users table exists.
  2. Server Configuration: Start your server (e.g., Apache with XAMPP).
  3. Access the Application: Open http://localhost/php-mvc-example/users to see the user list or http://localhost/php-mvc-example/users/create to add a new user.

And that’s it! You now have a basic MVC application in PHP. This structure is scalable and modular, allowing you to add new features or improve existing functionality without major structural changes.

php

Comparte el post y ayúdanos

También te puede interesar