Simple MVC for PHP

I consider myself a developer. Not a Java, Python or .NET developer. Just a developer. I don’t believe the language should have a significant impact in the kind of code you create and that’s why I enjoy creating well written applications in any language I happen to be using.

This is also true for PHP. Why do I mention PHP specifically? Because this is a language that has been usually associated with hard-to-understand, ugly code. I’m not going to get into this subject since a lot has already been written. Fortunately, thanks in part to frameworks like CakePHP, this idea is changing. I would strongly recommend using it for any moderate size application. But what should you do if you need a simple two-page site and have little time to learn a new framework? I want to show you that is easy to follow well known practices (like MVC in this case) in PHP with little effort.

In my case, I’ve recently been working in a big PHP site developed years ago and it was actually a nightmare. Business and view logic were mixed up in ways you don’t even imagine. So when I started a small site for a marketing campaign, I wanted to do it the right way. Since I didn’t want to use CakePHP (or any other similar framework) for something so small I decided to implement MVC by hand.

My application would consist in a simple model (plain PHP classes like User, Country, Score), several views (mainly HTML with minimal inline PHP) and the controllers. The main task here was to create a base controller to handle the interaction between the view and the model. I wanted to implement controllers like this:

class IndexController extends Controller {
    protected function get() {
        return new View('../resources/view/home.php');
    }
}

$controller = new IndexController();
$controller->start();

This is a simple controller with no logic. It just displays home.php (which is purely HTML).

A more complex case could be something like this:

class UserHomeController extends Controller {

    protected function get() {
        $user = User::getLoggedUser();
        if ($user != null && $user->isRegistered()) {
            $scores = Score::get($user);
        }

        return new View('../resources/view/homeuser.php', array(
            'scores' => $scores,
            'user' => $user));
    }
}

$controller = new UserHomeController();
$controller->start();

In this case we retrieve the logged user and his score from the database. This information is passed to the view using the View object.

Finally, the controller for a registration form which needs to handle GET and POST requests in different ways:

class RegistrationController extends Controller {

    protected function get() {
        // Render registration form
        return new View('register.php', null, View::REDIRECT_ACTION);
    }

    protected function post() {
        // Persist user
        $user = new User();
        $user->name = $_POST['name'];
        $user->email = $_POST['email'];
        $user->setBirthdate($_POST['birthdate_year'], $_POST['birthdate_month'], $_POST['birthdate_day']);
        $user->registration_date = Date::now();

        if ($user->isValid()) {
            User::save($user);
        } else {
            header('HTTP/1.1 500 Internal Server Error');
            exit;
        }

        // Redirect to user home
        return new View('userhome.php', null, View::REDIRECT_ACTION);
    }
}

$controller = new RegistrationController();
$controller->start();

As you can see, the main idea is to separate logic completely from the view and, additionally, we can place common controller logic (session management for instance) in one place.

The BaseController itself is a simple class that can be reuse in any project:

/**
 * Base Controller for all pages
 * Handles session management, GET/POST requests and response rendering
 *
 * @author german
 */
class Controller {

    /**
     * This method should be called from the controller PHP to handle
     * the current request
     */
    public function start() {
        session_start();
        $this->init();

        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
            $view = $this->post();
        } else {
            $view = $this->get();
        }

        if ($view != null) {
            $this->display($view);
        }
    }

    /**
     * Override this method to initalize the controller before handling
     * the request
     */
    protected function init() {
    }

    /**
     * GET request handler
     */
    protected function get() {
        $this->process();
    }

    /**
     * GET request handler
     */
    protected function post() {
        $this->process();
    }

    /**
     * Request handler. This method will be called if no method specific handler
     * is defined
     */
    protected function process() {
        throw new Exception($_SERVER['REQUEST_METHOD'] . ' request not handled');
    }

    /**
     * Populates the given object with POST data.
     * If not object is given a StdClass is created.
     * @param StdClass $obj
     * @return StdClass
     */
    protected function populateWithPost($obj = null) {
        if(!is_object($obj)) {
            $obj = new StdClass();
        }

        foreach ($_POST as $var => $value) {
            $obj->$var = trim($value); //here you can add a filter, like htmlentities ...
        }

        return $obj;
    }

    private function display($view) {
        if ($view->action == View::RENDER_ACTION) {
            $context = $view->context;
            include($view->url);
        } else if ($view->action == View::REDIRECT_ACTION) {
            header('Location: ' . $view->url);
        } else {
            throw Exception('Unknown view action: ' . $view->action);
        }
    }
}

class View {
    const RENDER_ACTION = 'render';
    const REDIRECT_ACTION = 'redirect';

    public $url;
    public $context;
    public $action;

    public function __construct($url, $context=array(), $action=View::RENDER_ACTION) {
        $this->url = $url;
        $this->context = $context;
        $this->action = $action;
    }
}

I don’t want to keep adding code to this post but you can imagine how the views are implemented. Anything included in the $context variable (View object) is available in the view to display it.

As you can see there’s really no reason to write spaghetti code in PHP (or any other laguange!) and even small project can be implemented in a nice and elegant way.

This is my small contribution to erradicate that old PHP myth…

Tags: ,

2 Responses to “Simple MVC for PHP”

  1. mario Says:

    While it leads indeed to more maintainable code than intermingling SQL with HTML and processing logic, the MVC concept as implemented in PHP frameworks is mostly a buzzword.
    http://stackoverflow.com/questions/1549857/simple-php-mvc-framework/1549970#1549970

    It would help newcomers more if contemporary concepts (MVVM) and terminology (Model-View-Presenter) would be adopted over the unoptimal Passive-MVC structure. Hoepfully in 2011 we’ll see some refreshed frameworks..

  2. dave Says:

    Very nice! Thank you! Is there somewhere to get the complete code?

Leave a Reply


Get Adobe Flash player