DEV Community

Micael Vinhas
Micael Vinhas

Posted on

PHP MVC: the easy way

Get to know MVC better and learn why it's so great.

Tired of getting confused about how to structure your application? You start small and simple but suddenly you have a bunch of files organized almost randomly? You are not alone, and you are not facing a minor problem. An organized application improves code maintainability and reusability and helps other people to cooperate with you.

In the old times, the usual way of writing applications was by using procedures and global states. We used to write everything on the same file: Infrastructure, Presentation, UI, you name it. Over time, the files got bigger and bigger and we struggled to know where the database operations start and where the UI ends.

This is where a Separation of Concerns (SoC) comes in handy. By splitting up concepts, the code gets more organized, and you can take it to the next level by creating layers for each concern. You can either find your own way to structure the application or use a proven pattern, like MVC.

MVC stands for Model-View-Controller. This is a layered architecture, which means that the main goal is to separate different components of an application. There are many more, such as MVVM (Model-view-viewmodel), but this is the most common one. Almost every famous PHP framework uses it by now (Symfony, Yii, Laravel, and CodeIgniter to give some examples).

By definition, MVC is a paradigm that divides the application into three layers: Model, View, and Controller.

A simple MVC diagram as found on Wikipedia

When writing a form, you are essentially dealing with the three layers. Writing is interacting with the View. Clicking on Save is sending instructions to the Controller, so it can tell the Model to save your data.

Some examples will be provided as we explain the three layers more deeply.

The Model

This layer is responsible to handle all the data. Every action that the user triggers on the application requires some sort of operation on the database. Someone should be responsible to ask the database to change it, and that will be the Model.

<?php

namespace Cv\Models;

use Cv\Db;

class Resume
{
    public static function getSections()
    {
        return Db::select(['*'], 'resume_sections');
    }

    public static function getEntries($section)
    {
        return Db::select(
            ['*'],
            'resume_section_entries',
            ['section_id = '.$section->id]
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

There are a couple of extra things worth noting: we are assuming that a class called Db exists and handles querying the database. In that database, two tables, resume_sections, and resume_section_entries, represent an entire Resume: resume_sections divide the Resume and resume_section_entries describe each Resume section.

The View

This layer handles what you see on your screen. It can communicate with both Model and Controller, and its main purpose is to give Model some sort of UI. Think of it as a representation of data for human interaction.

View: Resume/index.php

<div class="container">
    <?php foreach ($data ?? (object)[] as $section) : ?>
        <section class="resume" id="<?= trim(strtolower($section->title),' ') ?>">
            <div class="resume-content">
                <h2 class="mb-3"><?= $section->title ?></h2>
                <?php foreach ($section->entries ?? (object)[] as $entry) : ?>
                <div class="mb-3 content">
                    <div class="content-body">
                        <h3><?= $entry->position ?></h3>
                        <div class="subhead mb-1"><?= $entry->at ?></div>
                        <p><?= $entry->description ?></p>
                    </div>
                    <div class="content-footer">
                        <?= $entry->from ?> - <?= $entry->to ?? 'Present' ?>
                    </div>
                </div>
                <?php endforeach; ?>
            </div>
        </section>
        <hr class="m-0">
    <?php endforeach; ?>
</div>
Enter fullscreen mode Exit fullscreen mode

Nothing fancy here. The view is expecting a $data object to fill the resume on the frontend.

There is one issue, though. You should separate at all costs PHP from HTML, but this is only achievable with a template engine like Twig. Since we don't want to introduce more concepts here, let's keep Twig out of our examples. We will get back to it in another post.

The Controller

The Controller handles organizing and adapting both View and Model. It can receive data from the View and tell the Model what to do, and also it can receive data from the Model and arrange it so it can be comprehended by the user when interacting with the View.

Controller: ResumeController.php

<?php

namespace Cv\Controllers;

use Cv\Models\Resume;

class ResumeController extends Controller
{
    public static function index()
    {
        $sections = Resume::getSections();
        foreach ($sections as &$section) {
            $section->entries =
                Resume::getEntries($section);
        }
        return self::view($sections);
    }
}
Enter fullscreen mode Exit fullscreen mode

In this controller, self::view is responsible to call the view and pass *$section*s on it.

But where is the view function?

Perhaps you already noticed that this controller extends Controller. When extending another class, you are inheriting any methods of that class that are not private, and you can use them if you like.

Let's get to know this Controller to get a grasp of what we are doing here.

Main Controller: Controller.php

<?php

namespace Cv\Controllers;

class Controller
{
    public static function view(object $data = null)
    {
        include __DIR__.
            '/../Views/'.
            self::path().
            '/'.
            self::file();
    }

    private static function path() : string
    {
        return str_replace(
            'Controller.php',
            '',
            end(
                explode(
                    '/',
                    debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['file']
                )
            )
        );
    }

    private static function file() : string
    {
        return debug_backtrace(
            DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]['function'].'.php';
    }
}
Enter fullscreen mode Exit fullscreen mode

This code itself is not essential to understanding this paradigm, but this way you get a rough idea of how a simple MVC application should work.

Naming conventions

You should create separate folders for different layers. In this case, Views, Models, and Controllers.

In the Models folder, every class file should be named by its section. For example Home.php for the homepage-related stuff, Resume.php for everything related to the resume, and so on.

In the Controllers folder, you should give the classes a similar name to the model classes, but with "Controller" suffix. For example, HomeController.php. Mind that nothing here is mandatory, just a few common practices.

The view name should match the controller method. So, if you are writing a function called index for the HomeController, the view should be Views/Home/index.php. This is a great way to understand what that particular View is about. Also, if you need to change its behavior, you already know what method you should change.

Finally, the application directories should look like this:

App/
    Models/
        Home.php
        Site.php
        Resume.php
    Controllers/
        HomeController.php
        SiteController.php
        ResumeController.php
    Views/
        Home/
            index.php
        Site/
            index.php
            about.php
        Resume/
            index.php
            edit.php
            delete.php
Enter fullscreen mode Exit fullscreen mode

Now, a question. Looking at the above folder structure and respective files, how many methods do ResumeController.php have, and how are they called?

Final thoughts

This type of layered architecture is one of the most used because it's also easier to understand. Once you get used to it, you want to write all your applications using MVC patterns because they will look much more polished and organized. Other types of layered architecture have their merits but MVC represents a great foundation to write better software.

If you have any further questions please let me and readers know by using the comment section.


Further reading: Domain-Driven Design in PHP -- you can get here some MVC examples on the Chapter 2: Architectural Styles.

Latest comments (0)