web development articles and portfolio

Custom PHP MVC Tutorial: Part 2, URL mapping and index.php

Posted on 14-10-2011

One of the universal characteristics seen in MVC frameworks across platforms and languages is the structure of the URLs that the site/app works on - http://domain/controller/action/id - which is what we'll roll with here too. To do this for our custom PHP MVC framework, we'll need to utilize .htaccess URL re-writing with Apache.

URL re-writing in Apache comes about thanks to the module 'mod_rewrite'. From my experience, the vast majority of web hosts support this now days and if they don't, you may be able to contact your host to organize getting it enabled (If you're out of luck entirely, it should be noted that this URL rewriting is not really required; it just makes accessing the framework look much nicer for the end user).

To leverage the power of mod_rewrite, we'll need to create a file named .htaccess (note the period at the start). This file will sit in our site's root directory. The purpose of this file is to configure Apache and its modules on a per site/directory basis. In our case, .htaccess will look like this:

Options +FollowSymLinks
RewriteEngine on
RewriteRule ^([a-zA-Z]*)/?([a-zA-Z]*)?/?([a-zA-Z0-9]*)?/?$ index.php?controller=$1&action=$2&id=$3 [NC,L]

So what are we doing here? The first two lines are preparing Apache for our new rewrite rule, which we're defining using the RewriteRule command. This command first has a regular expression and is then followed by a generic URL with some variables in it. Effectively what this rule is saying is any URL that a user requests that matches this regular expression should be converted to the specified true URL at a web server level - however, the user never sees this conversion. Any matches inside the regular expression's round brackets are given a variable from left to right - the first being $1, the second being $2, and so on. This specific rule also allows for anything after the controller to be left off the URL (the question marks allow for this).

This is just a very basic .htaccess handling only the generic MVC styled URL. Depending on what your site/app actually does, you may want to enhance this - for example, a blog site might want article titles in the URL for SEO purposes, so an entry in your .htaccess would need to be added for that. Also keep in mind that in this state, a user guessing the true URL structure would still get the right page - with mod_rewrite and .htaccess, you could create a rule which requires all access to be achieved via the desired MVC styled URL.

A key component of this rule is its reference to index.php. As stated in part 1, this is our "landing page" for all requests in the MVC framework, and this rule is what enforces all MVC styled URL requests through index.php.

Now that we have our web server pointing to index.php, we can proceed to code it. This "landing page" is actually quite minimal lines-of-code wise as, by design, most of our code exists in our classes.

//require the general classes
require("classes/loader.php");
require("classes/basecontroller.php");
require("classes/basemodel.php");
//require the model classes
require("models/home.php");
//require the controller classes
require("controllers/home.php");
//create the controller and execute the action
$loader = new Loader($_GET);
$controller = $loader->CreateController();
$controller->ExecuteAction();

Basically Index.php is 'requiring' all the classes we need, and then it uses three lines to setup the controller and execute the action. Keep in mind any class you want the framework to be able to create instances for needs to be included/required here in Index.php (including any 'base' classes, which are covered in the controller and model sections of this article).

And that's it! Ok, not quite - I should probably cover the Loader class too, as it is obviously doing a lot of the work:

class Loader {
private $controller;
private $action;
private $urlvalues;
//store the URL values on object creation
public function __construct($urlvalues) {
$this->urlvalues = $urlvalues;
if ($this->urlvalues['controller'] == "") {
$this->controller = "home";
} else {
$this->controller = $this->urlvalues['controller'];
}
if ($this->urlvalues['action'] == "") {
$this->action = "index";
} else {
$this->action = $this->urlvalues['action'];
}
}
//establish the requested controller as an object
public function CreateController() {
//does the class exist?
if (class_exists($this->controller)) {
$parents = class_parents($this->controller);
//does the class extend the controller class?
if (in_array("BaseController",$parents)) {
//does the class contain the requested method?
if (method_exists($this->controller,$this->action)) {
return new $this->controller($this->action,$this->urlvalues);
} else {
//bad method error
return new Error("badUrl",$this->urlvalues);
}
} else {
//bad controller error
return new Error("badUrl",$this->urlvalues);
}
} else {
//bad controller error
return new Error("badUrl",$this->urlvalues);
}
}
}

When an instance of the Loader class is created, we opt to pass in the URL values, which PHP stores in the $_GET array. The Loader construct (called automatically on instance creation) stores $_GET into a private property, and then proceeds to extract our requested controller and action values from the URL values into private properties of their own, assuming some validation is met - if these values are empty, we assign default values of "home" for the controller, and "index" for the action. This is just a very basic level of validation, and it's something you'd probably want to flesh out in more detail in a live system. For instance, you may want to include checks for invalid characters.

If we go back to index.php for a second, you'll see what we do next is assign $controller to the return value of $loader->CreateController(). This method, as defined in the Loader class, returns a new instance of the class for the controller we requested, also assuming validation is met. The validation I have included checks to make sure the request controller class exists (it exists if it has been included in index.php), if the requested controller's class has a method matching the requested action, and also if our controller extends the base controller class (discussed later on). If any validation fails, we instead return an instance of the "Error" class, which is a fictional controller class designed to handle common error reporting.

You may notice we are also passing two values into our controller class when creating an instance:

return new $this->controller($this->action,$this->urlvalues);

These two values are our requested (and already validated as being available) action/method, and the URL values array, in case we need any extra URL values like Id available to the controller. If we go back to index.php, we'll see that after creating an instance of the requested controller class we then execute $controller->ExecuteAction(). This is a method defined in our base controller class which all controller classes needs to extend/inherit, which is the next topic of discussion in part 3.

Part 1 - Introduction
Part 2 - URL mapping and index.php
Part 3 - Controllers
Part 4 - Models
Part 5 - Views
Part 6 - Where to now

Social

Tags

Comments

comments powered by Disqus
This Website