Building a simple MVC system with PHP5
(Page 3 out of 5)The Model
The 'M' or model part of the MVC system is responsible for querying the database (or another external source) and providing the data to the controller. We could have the appropriate model loaded depending on the request, but I prefer to blur the lines between the model and the controller, whereby the controller uses a DB abstraction library to directly query the DB, instead of having a separate model. You might want to do it differently, but this is a personal preference.
One thing we must do is add the code necessary to setup up a connection with the database, and add it to our index page. There are many great DB abstraction libraries available (including my own, AutoCRUD) but PHP5 comes with a great DB library already - PDO - so there's no need for a different library.
Put the following code in the index file (below the inclusion of the startup file):
$db = new PDO('mysql:host=localhost;dbname=demo', '[user]', '[password]');
$registry->set ('db', $db);
In the above example we first create a new instance of the PDO library, and connect to our MySQL database. We then make the $db global, by using our Registry class.
The model part of our system is pretty much finished now, so let's move on to the next part: writing the controller.
Writing the controller also means we will have to write a Router class. A Router class is responsible for loading the correct controller, based on the request (the $router variable passed through the URL). Let's write the Router class first.
The Router class
Our Router class will have to analyze the request, and then load the correct command. First step is to create a basic skeleton for the router class:
Class Router {
private $registry;
private $path;
private $args = array();
function __construct($registry) {
$this->registry = $registry;
}
}
?>
And then add the following lines to the index.php file:
$router = new Router($registry);
$registry->set ('router', $router);
We've now added the Router class to our MVC system, but it doesn't do anything yet, so let's add the necessary methods to the Router class.
The first thing we will want to add is a setPath() method, which is used to set the directory where we will hold all our controllers. The setPath() method looks like this, and needs to be added to the Router class:
$path = trim($path, '/\\');
$path .= DIRSEP;
if (is_dir($path) == false) {
throw new Exception ('Invalid controller path: `' . $path . '`');
}
$this->path = $path;
}
Then add the following line to the index.php file:
Now that we've set the path to our controllers, we can write the actual method responsible for loading the correct controller. This method will be called delegate(), and will analyze the request. The first bit of this method looks like this:
// Analyze route
$this->getController($file, $controller, $action, $args);
As you can see, it uses another method, getController() to get the controller name, and a few other variables. This method looks like this:
$route = (empty($_GET['route'])) ? '' : $_GET['route'];
if (empty($route)) { $route = 'index'; }
// Get separate parts
$route = trim($route, '/\\');
$parts = explode('/', $route);
// Find right controller
$cmd_path = $this->path;
foreach ($parts as $part) {
$fullpath = $cmd_path . $part;
// Is there a dir with this path?
if (is_dir($fullpath)) {
$cmd_path .= $part . DIRSEP;
array_shift($parts);
continue;
}
// Find the file
if (is_file($fullpath . '.php')) {
$controller = $part;
array_shift($parts);
break;
}
}
if (empty($controller)) { $controller = 'index'; };
// Get action
$action = array_shift($parts);
if (empty($action)) { $action = 'index'; }
$file = $cmd_path . $controller . '.php';
$args = $parts;
}
Let's go through this method. It first gets the value of the $route querystring variable, and then proceeds to split it into separate parts, using the explode() function. If the request is 'members/view' it would split it into array('members', 'view').
We then use a foreach loop to walk through each part, and first check if the part is a directory. If it is, we add it to the filepath and move to the next part. This allows us to put controllers in sub-directories, and use hierarchies of controllers. If the part is not a directory, but a file, we save it to the $controller variable, and exit the loop since we've found the controller that we want.
After the loop we first make sure that a controller has been found, and if there is no controller we use the default one called 'index'. We then proceed to get the action that we need to execute. The controller is a class that consists of several different methods, and the action points to one of the methods. If no action is specified, we use the default action called 'index'.
Lastly, we get the full file path of the controller by concatenating the path, controller name and the extension.
Now that the request has been analyzed it's up to the delegate() method to load the controller and execute the action. The complete delegate() method looks like this:
// Analyze route
$this->getController($file, $controller, $action, $args);
// File available?
if (is_readable($file) == false) {
die ('404 Not Found');
}
// Include the file
include ($file);
// Initiate the class
$class = 'Controller_' . $controller;
$controller = new $class($this->registry);
// Action available?
if (is_callable(array($controller, $action)) == false) {
die ('404 Not Found');
}
// Run action
$controller->$action();
}
After having analyzed the request with the getController() method, we first make sure that the file actually exists, and if it doesn't we return an simple error message.
The next thing we do is include the controller file, and then initiate the class, which should always be called Controller_[name]. We'll learn more about the controller later on.
Then we check if the action exists and is executable by using the is_callable() function. Lastly, we run the action, which completes the role of the router.
Now that we have a fully working delegate() method, add the following line to the index.php file:
If you now try to run the system, you will either get the following error, if you haven't yet created the 'controllers' directory:
Or you will get the '404 Not Found' error, because there are no controllers yet. But that's what we're going to create right now.
August 24th, 2006 at 5:58 pm
Hi,
thanks for this useful article about the MVC system.
Just to report a problem using function trim in the Router’s setPath method (line 14) under UNIX systems.
If we give it an absolute path, it will delete the leading / and create problem when checking if this path is a directory.
August 28th, 2006 at 12:21 pm
Why you haven’t used the magic overloading methods __set and __get, instead of using registry class? It’s more easy to use __set, __get with static propery, because in future you don’t need to use methods get()/set(). Anyway, I’m using something like you have been described here for compatibility with PHP4. Instead of class static property I’m using container function, because in PHP4 there are no chance to declare static property in class body.
August 29th, 2006 at 9:38 am
Hello, good article about the MVC for starting.
September 4th, 2006 at 5:09 pm
Pretty cool article Dennis. Thanks. Good to see a relatively easy explanation of an MVC system. The thing with MVC is that the basics is fairly simple. But it can get complicated quickly when you throw in some locators, observers, datamappers and other classes and patterns.