gosukiwi's blog

Creating a PHP MVC framework - Part 2

Hello! And welcome back to the second part of the PHP MVC framework tutorial! In this part we’ll get our hands dirty with some real code, if you haven’t already, you should read the previous part of this tutorial.

Before we start

Have in mind we are actually reinventing the wheel! It’s a common story already, you see library X, you really like it, but it doesnt quite work as you want to, you don’t quite like the way it solves X problem, or you think you can do everyone a favour by doing the “ultimate X library”, it will be easier to use, faster, better, stronger, etc, most of the time you just end up worsening the initial problem!

xkcd's view on new standards

xkcd’s sums it up pretty nicely, you can replace standard with library, text editor, or prety much anything.

Don’t get me wrong! The fact that this happens is not that bad, as some of the new standards are actually better, for example, I personally believe that JSON is a win over XML, and so is YAML.

In this case one could argue it’s understandable as we are just doing this as a learning process, nevertheless I don’t discourage you to try and improve the framework or even create your own from scratch!

Don’t get dissapointed to create anything new, new things are cool, but have in mind it’s not as easy as it seems!

Now with that in mind, let’s start our framework :)

Directory Structure

Let’s start off by defining our directory structure, because PHP uses composer for dependency management, we’ll create a vendor/ folder, all of composer’s managed dependencies will reside there, as that is composer’s configuration by default. Even though we are “reinventing the wheel”, we don’t have to start from scratch! There are already some nice libraries we can use to save us some work!

Our application will reside inside app/, as it’s somewhat a standard name for the main application folder. Inside that folder, we’ll create three more folders: controllers, models, and views.

The entry point of the framework will be the index.php file.

Our structure so far looks like this

app/
|- controllers/
|- models/
|- views/
vendor/
index.php

Let’s now proceed to work on the index.php file, as this is the entry point of your application, it will have to do all the initial setup, for now, we have to:

  • Load a controller, and instantiate it
  • Call the appropiate function of the controller

Which controller should we load? Well here is where the routing concept kicks in, for now let’s just load the controller and action (the function which will be called) from reading the URL, this is not very versatile for custom routes, but it’s quite a good starting point.

So let’s assume our website is hosted at http://localhost/mvc-tutorial, if we wanted to call the ‘Main’ controller and the ‘index’ method, we should point our browser to http://localhost/mvc-tutorial/index.php/Main/index, so the format looks like {base-url}/index.php/{controller}/{action}.

This is what the initial index.php file looks like

<?php

// This is our framework setup
// The first thing we want to do is load the appropiate controller and call
// the required function.
// There are several ways to accomplish this, for now let's add a formatting
// rule to our URLs, they must be in the following format
//
// http://mysite.com/index.php/{controller_class}/{function_name} 
// 
// Notice the index.php part, we can later remove this by using URL Rewriting.

// The first step is to fetch the requested URI, we can use PHP's $_SERVER array
// to get the REQUEST_URI, which will look something like /index.php/hello/world
$uri = $_SERVER['REQUEST_URI'];

// Make sure the $url always ends with '/'
if(strrpos($uri, '/') + 1 != strlen($uri)) {
    $uri .= '/';
}

// Let's jump straight to after index.php/, as that's the part we are concerned
// about
$route = substr($uri, strpos($uri, 'index.php/') + strlen('index.php/'));

// We can now get the controller and action
// Let's split the string using '/' as a delimiter, the first result is the
// controller, the second is the action
$params = explode('/', $route);
// If no controller is specified, use 'MainController'
$controller = $params[0] ? $params[0] : 'Main';
// If no action is specified, use 'index'
$action = count($params) > 1 ? $params[1] : 'index';

// Now call the according controller
// First, append 'Controller' to the controller name, so we end up with names
// such as 'MainController', 'UsersController', etc.
$controller .= 'Controller';
// This is the path to the controller file
$ctrl_file = __DIR__ . '/app/controllers/' . $controller . '.php';

// If the controller does not exist, throw an exception
if(!file_exists($ctrl_file)) {
    throw new Exception("Controller $controller was not found.");
}

// If the controller does exist, include it and call the action/function
require($ctrl_file);
$ctrl = new $controller;
$ctrl->$action();

That’s quite a lot of code! But don’t worry, it’s not complicated, and it’s mostly comments, note that we use REQUEST_URI to get the full path, so we can work based on that URI, also, all our controllers must end with ‘Controller’, both the name of the file, and the class name, so the Main controller is a class named MainController in a file caleld MainController.php.

Something important to note is that, if no action is specified, it will default to the index action, something similar happens to the controller, if nothing is specified in the uri it will default to Main.

If you run the code it will throw an exception, as the MainController.php file does not exist, let’s fix that by creating the app/controllers/MainController.php with the following code

<?php
class MainController
{
    public function index() {
        echo '<h1>Hello, World!</h1>';
    }
}

The controller’s index function just returns a ‘Hello, World!’ message, if you now run your app, it will work, but it’s not very MVC oriented for the following reasons:

  • The data, in this case the ‘Hello, World!’ message, is hard-coded into the controller
  • The controller not only displays the actual data, but it also says how it should be displayed! (by using the h1 tag), this should be done by the view

Let’s fix those by

  • Getting the message from a model, which could fetch it from a database, a webservice or any other resource
  • Use a view to format the output

That way the controller just binds the model data and sends the data to the view, which displays it accordingly.

<?php
require(__DIR__ . '/../models/GreetingModel.php');

class MainController
{
    public function index() {
        $model = new GreetingModel();
        $greeting = $model->getGreeting();

        $this->view('Main/index.html.php', array(
            'greeting' => $greeting
        ));
    }

    private function view($file, $params = array()) {
        extract($params);
        require(__DIR__ . '/../views/' .$file);
    }
}

The GreetingModel is just a class with a getGreeting method, which returns our message, right now we don’t really care if it uses a database, a web service or whatever resource, once we get the data, we pass it to a view file, index.html.php, and pass our model data as parameter.

Finally, we add a helper view method which extracts the array keys and values as variables, setting up a context for the included file, which is actually included in the next line using require, that file can now access the greeting message by just using the $greeting variable.

For the sake of completeness, I’ll include the GreetingModel source code

<?php
class GreetingModel
{
    public function getGreeting() {
        return 'Hello, World!';
    }
}

And also the index.html.php file.

<!DOCTYPE html>
<html>
<head>
<title>Example View</title>
</head>
<body>
<h1><?php echo $greeting; ?></h1>
</body>
</html>

Our folder structure is now

app/
    controllers/
        MainController.php
    models/
        GreetingModel.php
    views/
        Main/
            index.html.php
vendor/
index.php

The following chart explains the flow of the application, which also describes the web MVC design pattern

Flow chart

As you can see, the first thing the app does when it gets a request from the browser is call index.php, this file sets up the according controller and calls the required action, then the controller uses the model (this step is optional), and finally returns a view, which is what the browser gets as response (this is also optional as redirection is handled by the controller).

If you have any error with your code or just want to download the full source code, you can check the git repository at GitHub. The repository uses tags as a way to go backward and forward in time, the repository for this tutorial is v1, for part 3 it will be v2 and so on.

Conclusion

So far we have created a simple MVC folder structure and worked on the entry point of our application, the index.php file, controllers, models and views are really simple, in fact, the controller and the model are just common PHP classes, and the view is just an html file with embedded PHP, but it hopefully demonstrate the concept that you don’t really need a fully featured framework to take advantage of a design pattern, in this case, web MVC.

We’ll also demonstrate later on why frameworks are so awesome, and all the things they ease for us, like base classes, helpers/libraries, dependency injection, and such.

Feedback is greatly appreciated! If you have something to say just leave a comment below, cheers!

Comments