Write your own PHP5 MVC framework

by blowfish

Let there be Lite

Although there are as many frameworks out there as there are programmers, very few frameworks allow the programmer room to code his/her own way. Most are bloated with libraries and features you will never use. And most impose arcane rules to application development, creating a whole new programming language the programmer now has to learn. Whereas our unnamed framework is not the answer to all your problems, it gives the web developer more leverage in deciding how to use the framework, changing the conventions, or even trashing it etc It is dead simple. And it would be useless without a simple do-it-yoursel tutorial of how it is built and how it works, because this is the whole point of wrestling control from popular mainstream frameworks and giving it back to the programmer.

Enough with the sales pitch.

(From Wikipedia)

Model–View–Controller (MVC) is an architectural pattern used in software engineering. Successful use of the pattern isolates business logic from user interface considerations, resulting in an application where it is easier to modify either the visual appearance of the application or the underlying business rules without affecting the other.

In simpler words-

1. Models handle database logic. As a matter of fact the model acts as an abstraction layer enabling us to access data (mostly from a database) as PHP objects or attributes of PHP objects.
2. Controller sit between the Models and the Views, passing requests from the views to the model and data from the model onto the view which the user interacts with.
3. Views are what the user sees when interacting with the application.

So with such a nice introduction we get down to it! We will create a PHP5 application to access a database of all our contacts. We will also provide forms to save contacts into the database.

In the beginning

We want our application to be at http://localhost/contacts/. The application will have pretty urls of the form

http://localhost/contacts/index/list_all -> to list all contacts in the db

http://localhost/contacts/index/delete/12 – to delete contact with the id 12

and so on and so forth ..

So we start off by creating the project directory `contacts`. Inside this directory we create a .htaccess file and put in the following rules :

Options -Indexes
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !(index.php|favicon.ico)$
RewriteRule (.*) index.php/$1 [PT,L]

Basically, the above rules allow you to implement pretty urls in the application. This is done by redirecting all requests in the background to index.php which in turn will pass the url parameters separated by front-slashes to bootstrap.php.

Note that your apache installation needs to have the directive AllowOverride set to All. Well not really All, but basically it must allow override of rules by .htaccess files.

index.php

Next we create the index file, index.php.

<?php
define(‘ROOT’, dirname(__FILE__));
require_once (ROOT . ‘/core/bootstrap.php’);

Notice that there are no closing tags in the above php file .. this is good standard practice for security purposes for files which do not contain any HTML content. Remember, index.php receives a string of parameters separated by the forward slash ‘/’ and will thus pass this onto bootstrap.php.

bootstrap.php

We create /contacts/core/bootstrap.php with the following..

<?php

$conf = parse_ini_file(‘./config/application.ini’, TRUE);

//[site]
define(‘BASE_DIR’, $conf['site']['BASE_DIR']);

//date.timezone setting
date_default_timezone_set($conf['time']['default_timezone']);

//path info
define (‘HTTP_PATH’, ‘http://’ . $_SERVER['SERVER_NAME'] . ‘/’ . BASE_DIR);
define (‘FS_PATH’, $_SERVER['DOCUMENT_ROOT'] . ‘/’ . BASE_DIR);
define (‘REQUEST_URI’, preg_replace(‘/\?’ . $_SERVER['QUERY_STRING'] . ‘/i’, ”, $_SERVER['REQUEST_URI']));

require_once (ROOT . ‘/core/init.php’);

The bootstrap basically initialises the application. The line :

$conf = parse_ini_file(‘./config/application.ini’, TRUE);

parses an .ini file for configurations which we have saved in the location /contacts/config/application.ini. These could be database credentials, the default time zone and any other configuration you will need. The lines which follow use the php function define() to define certain variables which will be used throughout the application. These defines are necessary because the $conf variable (or any $-variable for that matter) is limited in scope and will not be availabe within our class definitions in the very near future.

The last line :

require_once (ROOT . ‘/core/init.php’);

finishes off the initialisation process.

We save bootstrap in /contacts/core/bootstrap.php

init.php

We now begin prepare the way for some more interesting stuff here. We create /contacts/core/bootstrap.php with the following..

function callHook() {

$url = REQUEST_URI;

// strip out our query string
$url = preg_replace(‘/\?.+$/i’, ”, $url);
//explode() the string into an array of parameters
$urlArray = array();
$urlArray = explode(“/”, $url);
// for an empty string we assume the user wants to access index/index
if (!count($urlArray)) {
$urlArray[0] = “index”;
$urlArray[1] = “index”;
} else if (count($urlArray) < 2) {
$urlArray[1] = “index”;
}

$controller = $urlArray[0];
array_shift($urlArray);
$action = $urlArray[0];
array_shift($urlArray);
$params = array_map(‘urldecode’, $urlArray);

$controllerName = $controller;
$controller = ucwords($controller);
$controller .= ‘Controller’;

//we instantiate and call the controller methods
try {

if (class_exists($controller)) {
$dispatch = new $controller;
} else {
throw new Exception(“Invalid call to non-existent class {$controller}.”);

}

if (method_exists($dispatch, $action)) {

call_user_func_array(array($dispatch, $action), $params);
} else {

throw new Exception(“Invalid call to non-existent {$controller}::{$action}.”);

}
} catch (Exception $e) {

echo “<pre>” . $e->getMessage() . “</pre>”;

}

}

/** Autoload any classes that are required **/

function __autoload($className) {

if (file_exists(ROOT . ‘/db/’ . strtolower($className) . ‘.php’)) {
require_once(ROOT . ‘/db/’ . strtolower($className) . ‘.php’);
}

if (file_exists(ROOT . ‘/core/’ . strtolower($className) . ‘.php’)) {
require_once(ROOT . ‘/core/’ . strtolower($className) . ‘.php’);
}

if (file_exists(ROOT . ‘/lib/’ . strtolower($className) . ‘.php’)) {
require_once(ROOT . ‘/lib/’ . strtolower($className) . ‘.php’);
}

if (file_exists(ROOT . ‘/application/controllers/’ . strtolower(preg_replace(“/controller/i”, “”, $className)) . ‘.php’)) {
require_once(ROOT . ‘/application/controllers/’ . strtolower(preg_replace(“/controller/i”, “”, $className)) . ‘.php’);
}

if (file_exists(ROOT . ‘/application/models/’ . strtolower($className) . ‘.php’)) {
require_once(ROOT . ‘/application/models/’ . strtolower($className) . ‘.php’);

}

}

try {
callHook();

} catch (Exception $e) {

echo $e->getMessage();
}

Quite a mouthful, huh? Well, not really.

We receive the REQUEST_URI (minus the base url of our application ‘contacts’); for example

http://localhost/contacts/list_all/index is list_all/index.

The string ‘list_all/index’ exploded by the php explode() function into Array(’0′] => ‘list_all’, ['1'] => ‘index’)

http://localhost/contacts/list_all also becomes Array(’0′] => ‘list_all’, ['1'] => ‘index’) thanks to this block of code

if (!count($urlArray)) {
$urlArray[0] = “index”;
$urlArray[1] = “index”;
} else if (count($urlArray) < 2) {
$urlArray[1] = “index”;
}

The above block also generates Array(‘index’, ‘index’) from the url http://localhost/contacts.

Next is the block :

$controller = $urlArray[0];
array_shift($urlArray);
$action = $urlArray[0];
array_shift($urlArray);
$params = array_map(‘urldecode’, $urlArray);

$controllerName = $controller;
$controller = ucwords($controller);
$controller .= ‘Controller’;

Here, we assign $controllerName and $action which are, effectively, the Controller class to be instantiated and the method to invoke with the necessary parameters ($params) if the method requires parameters. The try/catch block that follows does all the above, additionally throwing an error if one occurs. Typical error scenarios include calling non-existent controller or its method; or calling a method without parameters where there needed to have been.

The __autoload function defined saves you the trouble of so many include’s or require_once’s on the hundred or so project files you might create on a bigger project.

Finally we callHook(), which basically executes our url as an object method.

So does this work? We will test it out shortly ..

Let there be light..

We create a directory application/ and inside it another directory controllers/. It is inside controllers/ that we will save all our controller classes; starting with our index file right here .. We create a file index.php and inside it the class IndexController

<?php

class IndexController {

public function index() {

echo “Finally, some light!”;

}

}

Now go the url http://localhost/contacts/index/index : ‘Finally, some light!’

Views

Being able to echo or print() content from the controller is all fine. But this doesnt scale up very well. It would be a nightmare not worth a framework if you had to echo all your HTML content.

Luckily, a View helper will help us out. The idea is to have different parts of a HTML page saved in a different directory as just .html or .php pages and then embedded into the correct template as necessary. Also we need to be able to embed templates within other templates with a great deal of flexibility. Afterall, this is the whole point of separation of content from logic.

We will be able to create a view , for example, named light.php and save it in a directory views/ inside application/. We should then be able to do the following to get ‘Finally, some light!’, only done in a smarter way!

<?php

class IndexController {

public function index() {

$view = new View(‘light.php’);

$view->message = “Finally, some light!”;

$view->render();

}

}

And just for fun, a nested view should be possible too :

$view = new View(‘light.php’);

$view->message = new View(‘light.php’);

$view->message->message = “Finally, some light!”;

$view->render();

The above feat is made possible by a few lines of code in the file view.php, which will reside in contacts/core/views.php

<?php

class View {

public $arr = array();
public $file;

public function __construct($file) {
$this->file = $file;
}

protected function get_sub_views($obj) {

foreach ($obj as $varname => $var) {
if ($var instanceof View) {
$obj->arr[$varname] = $var->get_sub_views($var);
} else {
$obj->arr[$varname] = $var;
}
}
extract($obj->arr);
ob_start();
if (file_exists(ROOT . “/application/views/” . $obj->file)) {
include ROOT . “/application/views/” . $obj->file;
} else {
throw new Exception(“The view file ” . ROOT . “/application/views/” . $obj->file . ” is not available”);
}
$html = ob_get_clean();

return $html;
}

public function render() {
echo self::get_sub_views($this);
}

}

Quite briefly, get_sub_views() recurses through the nested views and renders all the variables from the bottom of the recursion tree going up until at last it renders the base view. Magic? You tell me.

Models

We have taken care of the V and the C in MVC but what about the M ? For now it will suffice to say that we need the following 3 files saved in our contacts/db/. In one of the following blog posts I will go into what exactly happens inside db.php and dao.php, I will then break forth into poetry of how these two files with the help of PDO are the key to world peace and finding lasting love. But this (and more) will happen on another day.

contacts/db/

—->db_conn.php : will set up a database connection via PDO.

—->db : supports CREATE (insert), READ (select), UPDATE (update), DELETE (delete) methods to our mysql db. But why not just use PDO::query() to directly execute mysql queries ? Because we are trying to achieve database agnosticism. So that later when our contacts app changes its db from mysql to postgre, all we (the smarter) will need to is to change this wrapper to postgres, mssql or whatever other relational db they will hjave come up with then. The not-too-smart programmers will be re-writing the whole application from scratch!

—->dao

: This is an Object Relation Modeller (ORM) for our application. I called DAO (Data Access Object) instead because .. well, I didnt want to sound like everyone else ;-) . The DAO (plus adherence to some 3 or so database scheming conventions) will make db access and updating a breeze. as easy as

$contact= new Contact;

$contact->name = “blowfish”;

$contact->number = “254722115273″;

$contact->save();

Quite easy!

The database username and passwords reside in contacts/config/application.ini. For purposes of clarity, application.ini looks something like this :

[db]
host = localhost
username = db-username
password = db-password
name = contacts

[site]
BASE_DIR = biblia/

[time]
default_timezone = “Africa/Nairobi”

We now have a core system with which we can build an application.

Let the framework bring forth applications that the interwebs may teem with life ..

I suppose with that brief introduction to the application’s core we can begin bringing to life our Contacts app.

The contacts database

We create a mysql database named Contacts. And in it we create the table `contacts`.

create table contacts (id int auto_increment primary key, name char(30), number char(16), email char(50));

To leverage the goodies (and lasting love brought by) DAO we follow these 3 simple rules in our table schema

1. all table names are plural eg contacts, users, boxes etc

2. all tables must have an id field (with the exception of pivot tables)

3. foreign keys are named in the singular form of the parent table. This involves knocking off the last `s` from the parent table name and appending `_id` to it. Therefore, if we were to later have a `users` table with the contacts.id foreign key it would be contact_id`. A less interesting scenario is where the table name’s plural contains an `es` at the end. `Knives`, for example, will have its foreign key named `knive_id`. Dont blame the framework, blame the English language !

The Model

After creating the db table, we will create a model for it in contacts/application/models/contact.php.

<?php

class Contact extends DAO{

public function __construct($table = “contacts”, $id= null) {

parent::__construct($table, $id);

}

}

It is now possible to do

$contact = new Contact();

OR to select an existing contact by id

$contact = new Contact(2);

echo “{$contact->name} –> {$contact->number}”;

Adding data – The form view

we create application/views/add.contact.php with the followind code :

<form action=’<?=HTTP_PATH?>index/save’ method=’post’>

<p>

<label for=’name’>Name : </label>

<input type=’text’ name=’name’ />

</p>

<p>

<label for=’number’>Number : </label>

<input type=’text’ name=’number’ />

</p>

<p>

<label for=’email’>Email : </label>

<input type=’text’ name=’email’ />

</p>

<p>

<input type=’submit’ name=’save’ value=’Save’ />

</p>

</form>

Adding data – The controller `add` method

Inside contacts/application/controllers/index.php ..

<?php

class IndexController {

public function index() {

$view = new View(‘light.php’);

$view->message = “Finally, some light!”;

$view->render();

}

public function add() {

$view = new View(‘add.contact.php’);

$view->render();

}

}

And now the `save` method in IndexController

public function save() {

$contact = new Contact(); // no constructor arguments since we are creating a new Contact object

$contact->name = $_POST['name']; // in the real world, all $_POST and $_GETS _must_ me sanitized

$contact->number = $_POST['number'];

$contact->email = $_POST['email'];

if ($contact->save()) {

$view = new View(‘light.php’);

$view->message = “The new contact {$_POST['name']} has been saved!”;

$view->render();

} else {

$view = new View(‘light.php’);

$view->message = “There was a problem saving the contact {$_POST['name']} “;

$view->render();

}

}

`list_all` contacts in the db

We start off with the view : contacts/application/views/list.contacts.php

With this view we loop over a collection of contact objects and echo the rows in a nice table. We also add ‘delete’ link at the end of every column to enable delete non-forthcoming contacts (the guys who blocks your calls).

<table>

<?php foreach ($contacts as $contact) {?>

<tr>

<td><?=$contact->name?></td>

<td><?=$contact->number?></td>

<td><?=$contact->email?></td>

<td> <a href=’<?=HTTP_PATH?>index/delete/<?=$contact->id?>’>delete </a></td>

</tr>

<?php }?>

</table>

We create another method/action in IndexController to list our contacts. We will call tis `list_all` ..

public function list_all(){

// we select all the contacts in the db

$contacts = new Contact(‘*’); // ‘*’ is equivalent to `select * contact objects` in DAO-speak

//assign a view

$view = new View(‘list.contacts.php’);

$view->contacts = $contacts;

}

We create the delete method/action :

public function delete($id){

// this $id is the integer immediately following http://../index/delete/<integer>

$contacts = new Contact($id); // now here we select a contact with the id equal to the value of $id

if ($contact->del()) {

$view = new View(‘light.php’);

$view->message = “the contact has been deleted”;

} else {

$view = new View(‘light.php’);

$view->message = “there was a problem deleting the contact”;

}

}

And that is it!

Now we look on all our (brief but smart) works in awe, thank the Good Lord above .. and rest

See you later when I explain how the DAO class works, and most importantly how you too can build your own.