Writing a Minimalistic MVC Framework in PHP

This 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:

croppercapture97.jpg

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.

This entry was posted in programming and tagged , . Bookmark the permalink.



23 Responses to Writing a Minimalistic MVC Framework in PHP

  1. Volomike UNITED STATES Mozilla Firefox Ubuntu Linux says:

    Cool. I’m not a fan of MVC, but some clients require it, so this might be the one to use with some adaptation. I also wanted to state that on Ubuntu Linux, I highlighted the text from the headline to the very last word, then opened OpenOffice, pasted it in there, and then clicked the PDF button on the toolbar to export this into a PDF for later reading in case this blog ever goes down. I was amazed how well that worked in OpenOffice.

    I’ve also had a stroke of genius today too, but on a different topic. You can integrate C with PHP fairly easily. And yesterday’s interesting article that I wrote was on something I made up called CCAPS. Perhaps you’ll find either of those interesting.

    Reply  |  Quote
  2. IceBrain PORTUGAL Mozilla Firefox Debian GNU/Linux Terminalist says:

    I’ve never quite understood the “Controller” in webapps. I mean, isn’t the webserver the controller, choosing the PHP file depending on the URL? What’s the difference between calling http://www.example.com/?module=user and http://www.example.com/user (with mod_rewrite, of course). This way we can enjoy server features like redirecting specific pages requests to another servers or mixing well PHP and static pages.

    And if the controller is out, we just get Model and View, that is basically templating.

    Reply  |  Quote
  3. Volomike UNITED STATES Mozilla Firefox Ubuntu Linux says:

    [quote post="2479"]I’ve never quite understood the “Controller” in webapps. [/quote]

    I agree with you there, IceBrain. I really don’t see the point of a controller, and a model is nothing more than a place to stick reusable code. I’ve seen some super-fat controller functions that made no sense. There’s no guideline on when to make another controller, so you see people sticking a bunch of stuff in a controller function. Therefore, the best option seems to be templating.

    Plus, like you, I agree that mod rewrite is very essential today in our world of SEO-optimized URLs. I mean, if you create a blog system, you want the URL to be like:

    http://mysite/articles/this-is-my-unique-title

    …where articles.php is receiving “/this-is-my-unique-title” for parsing, and mod_rewrite in Apache lets us drop the .php and pass it anything that comes after it. But with MVC, it’s not very friendly with this sort of system.

    And on the templating front, I used to use Smarty until I figured out that (a) it slowed the server down, (b) PHP is already a great templating language, (c) PHP developers already know PHP — no need to learn someting else, and (d) you can implement PHP alternative syntax in a strict manner and separate XHTML from PHP very nicely. So that’s why I invented CCAPS.

    So, yeah, MVC is a fad. But if a client wants me to use it, I’ll sure stick with something thin like this.

    Reply  |  Quote
  4. Luke Maciak UNITED STATES Mozilla Firefox Ubuntu Linux Terminalist says:

    @IceBrain: Good question. Conceptually I see the distinction as:

    Model is where you stick the database specific logic.
    View is where you stick display specific logic.
    Controller is where you stick business logic.

    But your question is valid because the whole concept of business logic can be fuzzy. As a rule of thumb I’d say it is anything that has to be done in software (ie. cannot be done as part of a query/stored procedure) but has nothing to do with displaying the data on the page. So every module will have it’s own unique controller with the business logic that it specific to it.

    As for the central entry point for the application – it facilitates code reuse and forces you to use logical URI scheme. In a production environment I would use mod_rewrite to turn ugly addresses such as:

    http://example.com/?module=user&action=view&id=5890

    into sexy ones like:

    http://example.com/user/view/5890

    It makes all the different parts of the system behave in a predictable way.

    Reply  |  Quote
  5. PCSpectra CANADA Internet Explorer Windows says:

    I’m always interested in reading others opinion on MVC.

    Unfortunately i would not really call what you have here a framework, as I could not easily reuse any of it and it does not promote clean separations.

    What you have done is demonstrate the very basics of MVC.

    Reply  |  Quote
  6. drubin Mozilla Firefox Linux says:

    I was just wondering with the whole MVC approach why are you making use of php4 syntax for object/class design? over the php5 improved version?
    http://php.net/manual/en/language.oop5.php
    http://www.php.net/manual/en/language.oop.php

    Reply  |  Quote
  7. Luke Maciak UNITED STATES Mozilla Firefox Windows Terminalist says:

    @PCSpectra: Yeah, you are right. Not a framework but basic MVC skeleton.

    @drubin: Old habits die hard I guess. :) Also there is still one legacy app that I maintain which is still on php4 because upgrading totally breaks everything everywhere. It has been due for refactoring to work under php5 but it is not a priority and gets pushed for later every time it comes up. :P

    Reply  |  Quote
  8. dawn SWITZERLAND Mozilla Firefox Linux says:

    I think you should use the adjective “minimalist” instead of “minimalistic” because the latter is pejorative. I can’t find any definition backing it up right now but I’m pretty sure I’ve heard an English teacher comment on that nuance.

    Reply  |  Quote
  9. drubin Mozilla Firefox Windows says:

    @Luke Maciak:
    Makes sense I understand legacy code! (It sucks!!) maybe just include that in your blog post so that new users will maybe make the effort to change to the php5 syntax.

    I know that a lot of php newbies follow your blog (as it is a good blog) and might start with old habits :)

    ps Thanks for taking the time to write these blogs.

    Reply  |  Quote
  10. Jshpro2 UNITED STATES Mozilla Firefox Windows says:

    @luke

    business logic does not go in a controller, business logic should go in the model. Database code goes under or is a dependency of the model layer. Ideally you would have the thinnest controller layer possible.

    The reason for controllers, if if you’re programming your models to a common interface ( you are right? ) then the code to instantiate and setup those models should be identical, and therefore abstracted in a controller

    Reply  |  Quote
  11. PCSpectra CANADA Internet Explorer Windows says:

    The reason for controllers, if if you’re programming your models to a common interface ( you are right? ) then the code to instantiate and setup those models should be identical, and therefore abstracted in a controller

    I disagree. In my experience the code to instantiate a model or a view should be arbitrary, otherwise your controller is now conretely (is that a word) dependent upon a model/view interface.

    What happens if you need multiple models in your controller?

    Did I misunderstand something?

    This is how (I believe) CakePHP does it…it’s controllers automagically load the model and view which by convention (I assume) requires me to name my model/view by the same name or prefix as the controller being executed.

    Now only does it introduce that ‘concrete’ dependency I mention above but what about when a controller doesn’t need a model or a view? Now you have unnesccesary over head. IMHO it’s best to instantiate the model/view explicitly in the controller:action. This has the added benefit of being very obvious and more configurable at instantiation.

    Cheers,
    Alex

    Reply  |  Quote
  12. Jshpro2 UNITED STATES Mozilla Firefox Windows says:

    automagicness or not, controllers are dependent on the model layer. For most applications your models are going to follow a CRUD pattern ( create read update delete ), as such what I do is create a CRUD controller other controllers can extend from, they override any action ( overriding behaviour with NULL behavior to disable creations, reads, updates, or deletions for a given controller ).

    Basically the point over using a controller ( class ) over using a page controller ( a .php file for each page ) is to take advantage of OOP abstraction, cakes automatigical model and view loading for example, you end up writing less controllers to do the same job

    Reply  |  Quote
  13. PCSpectra CANADA Internet Explorer Windows says:

    automagicness or not, controllers are dependent on the model layer.

    Not nessecarily. Controllers don’t always need to have a model or a view.

    For instance a static page would not typically require a model as the view would/could be hardcoded directly into the controller. Likewise not every controller needs a view, such as a cron script which might only act on the model and redirect or whatever.

    Basically the point over using a controller ( class ) over using a page controller ( a .php file for each page ) is to take advantage of OOP abstraction, cakes automatigical model and view loading for example, you end up writing less controllers to do the same job

    In my experience, sometimes it makes sense to do a little extra work in favour of code clarity or introduce automagic dependencies.

    Using a base controller results in less flexible code IMHO or experience.

    I’m confused though…how do you use a base controller to handle your CRUD? You would still need to override the CRUD actions in a subclass and thus need to introduce the controller anyways, so how do you write less controllers? You just write a little less code, if anything.

    Reply  |  Quote
  14. Jshpro2 UNITED STATES Mozilla Firefox Windows says:

    Of course a controller action ( action level ) doesn’t require 100% of the time a dependancy to a model and a view, I’m saying in the MVC pattern the view fires events, the controller catches events. You are uncoupling the view from the model, by coupling the model to the controller. You can’t get rid of coupling you can only move it around to more desirable places. My argument wasn’t about how to write a CRUD controller but basically its called a state object, instead of subclassing an abstract class you have the object’s behavior change based on some internal flag, subclassing the object only for special case applications, since your controllers have an inheritance hierarchy you’re writing code only once. Less code? yeah, less controllers? depends on your coding style.

    Reply  |  Quote
  15. Jshpro2 UNITED STATES Mozilla Firefox Windows says:

    Also saying using a base controller causes less flexible code doesn’t make sense, your controllers need convenience methods like accessors to request objects, factory methods for generating views. Using a base controller in my experience has helped me not repeat myself, not produce less flexible code. Majority of the PHP MVC frameworks have you subclass a controller_action class or something of the sorts.

    Reply  |  Quote
  16. PCSpectra CANADA Internet Explorer Windows says:

    I initially wrote a long winded counter argument but after re-considering I really don’t think it’s appropriate to hijack this guys blog.

    If you wish to continue disscussion start a topic in T & D at DN and I’ll likely speak my two cents. :P

    Cheers,
    Alex

    Reply  |  Quote
  17. Jshpro2 UNITED STATES Mozilla Firefox Windows says:

    sounds good, PM it over :-)

    Reply  |  Quote
  18. Ernie AUSTRALIA Mozilla Firefox Ubuntu Linux says:

    From my personal use any CRUD functionality is in a parent model class that all models inherit from.

    As for handling clean (sexy) URLs, having some kind of routing schema helps. Horde has such functionality – http://dev.horde.org/routes/manual/index.html, and there is another here, which can be integrated in setups like this.

    Reply  |  Quote
  19. Volomike UNITED STATES Mozilla Firefox Ubuntu Linux says:

    Just an announcement. I’ve seen the light. I’m MVC all the way now. I mean, I was doing this sort of stuff, anyway, just not realizing it. The other day I started doing some control routing from incoming URLs, and then I said, “See? That’s the front controller you’re building.” I then started building a template class instead of using Smarty, and again I said, “See? That’s the view class.” So then I started learning about the difference between a front controller and page controllers, the purpose of models vs. controllers, and I then started evaluating frameworks. From there, I realized that the only way I would be satisifed was to make my own MVC framework, so I did it.

    So, another angel gets his wings in heaven.

    Reply  |  Quote
  20. Aksel ESTONIA Mozilla Firefox Windows says:

    @ Volomike:
    Reading this blog entry and especially your comments, I am now converting to MVC religion also.

    Reply  |  Quote
  21. Nice,

    the URL tips on commets is very helpful, great post!

    Reply  |  Quote
  22. @Luke, put your github account on social links too :)

    Reply  |  Quote

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>