Writing a Minimalistic MVC Framework in PHP
Wednesday, October 22nd, 2008This is an outcome of a conversation I had recently. Apparently saying that MVC is a fairly simple concept is some sort of blasphemy. But it is. You really don’t need to use the almighty RAILS or a rails like framework like CakePHP in order to the whole Model, View, Controller separation. That’s the beauty of the idea - you can write your own MVC framework from scratch quite easily. In fact, sometimes it is probably preferable to do so. It’s true that you should avoid reinventing the wheel but sometimes you just want lean, minimalistic code with as few dependencies as possible. This is where you would do something like the example below.
This is a bare bones minimalistic core of a MVC system. It’s the index file - the driver or super-controller whatever you want to call it. Users will hit this page, and it will hand off the control to an appropriate Controller class. Thanks to the malleability of PHP this can be accomplished in just few simple lines:
<?php define("COMMON", "/path/to/common/includes/"); include COMMON."Util.class.php"; if(empty($_GET["module"])) die(" <h1>Error: No Module Specified</h1> <p>Sorry, no module was specified.</p> <p>Please check your URL</p>"); if(empty($_GET["action"])) die(" <h1>Error: No Action Specified</h1> <p>Sorry, no action was specified.</p> <p>Please check your URL</p>"); $module = Util::sanitize($_GET["module"]); $action = Util::sanitize($_GET["action"]); $controller = $module."DefaultController"; if(file_exists("controllers/$controller.php")) { include "controllers/$controller.php"; $active_controller = new $controller(); if(!method_exists($active_controller, $action)) die(" <h1>Error: Action Not Defined</h1> <p>Sorry, the action <strong>$action</strong> is not defined in <strong>$module</strong>.</p> <p>Please check your URL</p>"); else $active_controller->$action($_GET); } else die(" <h1>Error: Module Not Found</h1> <p>Sorry, module <strong>$module</strong> does not exist.</p> <p>Please check your URL</p>"); ?>
Note that I’m using some shortcuts here. For example I assume that you can figure out for yourself how to sanitize user input strings. I moved that functionality to the Util class which I import up top. You probably figured it out already, but if you didn’t this is essentially the folder structure you will need to have in your webroot directory:

The rest of it should be straightforward. For example, let’s say you access this page with the following URI:
http://example.com/?module=user&action=view
This will trigger our file to try to locate the file user.php in the sub-folder controllers and import it. Then it will assume that the file contains a class user and will attempt to initialize it. Note that I did this in PHP4 so I’m using the old style constructors. You can easily modify it for PHP5. Finally, it will check if the newly created instance contains the method view and will attempt to execute it.
The controller class would then initialize a model class which would query the DB for user information, do whatever processing needs to be done and then pull in a View class which would deal with the displaying of the data on the page. It would probably utilize some templating scheme.
I’d actually implement a base class (let’s name it DefaultController) which would be the parent for all the other controller classes. In it I would implement all the basic CRUD functions in the most generic way possible. This way, when you decide to add a new controller to your project you can simply create an empty class stub which extends DefaultController and still have some functionality there. A very simplistic controller class would look something like this:
class DefaultController { var $model; var $view; function DefaultController() { // find out the name of this module so that we can apply it in other places $module = substr(get_class($this), 0, strpos(get_class($this), "controller")); $model_name = $module."Model"; $view_name = $module."View"; include "models/$model_name.php"; include "views/$view_name.php"; $this->model = new $model_name(); $this->view = new $view_name(); } function read($args) { if(empty($_GET["id"])) $this->idError(); $id = Util::sanitize($_GET["id"]); $this->model->populateFromDB($id); $this->view->render("read", $this->model); } function update($args) { if(empty($_GET["id"])) $this->idError(); if(!empty($_POST)) { $this->model->populate($_POST); $this->model->update(); } $id = Util::sanitize($_GET["id"]); $this->model->populateFromDB($id); $this->view->render("update", $this->model); } function create($args) { if(!empty($_POST)) { $this->model->populate($_POST); $new_id = $this->model->create(); $this->view->render("thankYou", array("caption" => "Record Inserted", "id" => $new_id)); } else { $this->view->render("create", $this->model); } } function delete($args) { if(empty($_POST)) { if(empty($_GET["id"])) $this->idError(); $id = Util::sanitize($_GET["id"]); $this->model->populateFromDB($id); $this->view->render("delete", $this->model); } else { $this->model->populate($_POST); $this->model->delete(); $this->view->render("thankYou", array("caption" => "Record Deleted")); } } function idError() { die("<h1>No Record Specified</h1> <p>You need to specify a record ID to perform this action.</p>"); } }
Again, this is really minimalistic code. As you can see there is no error checking and no validation. Most of the time I’m delegating control to other classes and making even more assumptions. For example I’m assuming that a model class would have methods such as populate (which would essentially pre-populate it’s fields from an associative array passed in as an argument) and populateFromDB which would query the database and populate it by using the stored data. You can’t really make the models too generic since their structure will be based on the database table layout. You could probably try something clever - such as using an associative array instead of defining fields of all the database columns. This way you could have a fairly generic structure.
class DefaultModel { var $fields; var $table_name; var $db; include COMMON."config.php"; include COMMON."MyDatabase.class.php"; function DefaultModel() { $this->table_name = substr(get_class($this), 0, strpos(get_class($this), "model")); $this->fields = array(); // associative array // the database stuff is defined in config.php imported above $this->db = new MyDatabase(__DB_HOST__, __DB_DATABASE__, __DB_USER__, __DB_PASSWORD__); $this->db->dbconnect(); } function populateFromDB($id) { $results = $this->db->query_into_array("SELECT * FROM ".$this->table_name." WHERE id='$id'"); $this->populate($results); } function populate($array) { foreach($this->fields as $key => $val) if(@key_exists($key, $array)) $this->fields[$key]["value"] = $array["$key"]; } function insert() { $sql = "INSERT INTO ".$this->table_name." (submitted_on"; foreach($this->fields as $key => $row) if($key != "id" and $key!='submitted_on' and $key!="last_updated_on") $sql .= ", $key"; $sql .= ") VALUES (NOW()"; foreach($this->fields as $key => $row) if($key != "id" and $key!='submitted_on' and $key!="last_updated_on") $sql .= ", '$row[value]'"; $sql .= ")"; $this->db->query($sql); return mysql_insert_id($this->db->getLink()); } function update() { $sql = "UPDATE ".$this->table_name." set last_updated_on=NOW()"; foreach($this->fields as $key => $row) if($key != "id" and $key!='submitted_on' and $key!="last_updated_on") $sql .= ", $key='$row[value]'"; $sql .= " where id='".$this->fields["id"]["value"]."'"; $this->db->query($sql); } function delete() { $this->db->query("DELETE FROM ".$this->table_name." WHERE id='".$this->fields["id"]["value"]."'"); } }
Note that I’m using a made up wrapper class instead of calling MySQL functions directly. This is just something that I did ages ago - I created a generic database class to interface with my code. Now whenever I switch database engines all I need to do is re-implement that class using appropriate syntax. Since all my SQL is going through that class I can even intercept it and correct illegal statements. For example my code might be using MySQL specific keywords, but the Postgress implementation of MyDatabase will automatically transform these queries into something that Postgress understands.
Then again, you may not want to do that. Parametrized queries may be a better idea here. They would be yet another step of abstraction. You could establish some common naming conventions then define parametrized queries in each database to follow them and simply have your wrapper class call them by name instead of actually building SQL queries.
Other than that the class is fairly generic - you will have to define the fields array on your own which can be easily done in the child class’ constructor. It’s not ActiveRecord or anything so there will be a considerable amount of work involved setting it up each time but it is serviceable.
I’m nor going to do a View class here because this post is getting crowded with code. But you get the idea - I’d use the same methodology as above, define as many generic methods as possible and then override them as needed in child classes.
I’m not sure how projects like CakePHP implement their MVC model. This is just an example of how I would go about developing one if I had to. It is probably very simplistic and unsophisticated compared to the other stuff available out there. But that’s sort of the point. Anyone can implement an MVC framework of sorts - for better or for worse. This one is mine. Yours may be very different but that’s ok. Constructive criticism is as always appreciated. Just remember that I pretty much pulled it out of my ass during a lunch break sort of as a proof that it is really not that complicated.
