Programming your own PHP framework Part 2 – MVC

This is part 2 of the programming your own php framework – view part 1. The below article was authored by Terry Smith. Terry is an aspiring entrepreneur and PHP code ninja working for b5media. He spends his “weekends” and “free time” working on all sorts of crazy ideas and one day wants to rule the world (or a small part of it)!

To get things started, you should be familiar with the Model-View-Controller paradigm.  The basics components are models (classes/objects/etc.) that represent items in your application (users, database records, etc.), controllers which do the processing for a page or module and views, which contain the HTML/CSS for your output.  You can read more about it here.

Step 1: Directory Structure

As noted in the last part, I have chosen to structure my URLs in the format

domain.com/[controller]/[module]?vars

In this case, the controller indeed represents the controller in the MVC paradigm.  I will take a moment to explain the directory structure I’ve used; again, note that you can use almost any structure for your own applications.

/config – Basic configuration files (database settings, etc.)
/controllers – Controllers are all in this directory.
/lib – Default library files included with every new site I deploy (database class, URL rewriting, templating and other base classes)
/models – Custom models for each application (users, sessions, etc.)
/views – View files (PHP files), that contain the HTML
/web – The actual web directory we point our web server to.
There are two things of note here.  First, our images, CSS, etc. go into our /web directory, since the web server can’t read anything above the /web folder.
Second, and most important, all of these files should be outside/above the directory you actually point your web server to serve files from.

Step 2: The only real file

So let’s start at the beginning, since that seems the most reasonable place.  Inside my /web directory, I have just one PHP file, index.php:

<?php
require(dirname(getcwd()) . "/lib/init.php");
RoutingController::getInstance()->route();
?>

As you can see, there isn’t too much to it. You may note that we’re using a singleton instance of the RoutingController; more on that later. We’re including the initialization file that comes standard with each deployment. This file will include all of the others and set up the rest of our system. Note: make sure you have mod_rewrite enabled and the .htaccess file in place from the last part.

Step 3: Initializing the system

So, let’s take a look at that initialization file:

<?php

// Are we currectly in production?
define('PRODSERV', false);

// Directory definitions
define('ROOT_DIR', dirname(getcwd()) . '/');
define('LIB_DIR', ROOT_DIR . 'lib/');
define('VIEW_DIR', ROOT_DIR . 'views/');
define('MODEL_DIR', ROOT_DIR . 'models/');
define('CONTROLLER_DIR', ROOT_DIR . 'controllers/');
define('CONFIG_DIR', ROOT_DIR . 'config/');
define('TEMPLATE_DIR', ROOT_DIR . 'templates/');

// Set up the library includes
set_include_path(CONTROLLER_DIR . PATH_SEPARATOR . LIB_DIR . PATH_SEPARATOR . MODEL_DIR . PATH_SEPARATOR . get_include_path());

// Include database settings
$dbini = parse_ini_file(CONFIG_DIR . 'database.ini');

// Get our connection to MySQL
$mysql = MySQL::getInstance();
$mysql->initialize($dbini['host'], $dbini['username'], $dbini['password'], $dbini['database']);

// Make sure we can autoload files
function __autoload($class)
{
        require("$class.php");
}

Let’s go through this part by part. The very first thing we do is create a define called PRODSERV which represents whether we’re running in a production environment or not. If we’re not, we can output error information, but we certainly wouldn’t want that in production. Next, we define all of the directories where our various files reside and make sure to let PHP know where they are by temporarily changing the include path. We can then load the database settings (I’m using INI files for ease of use, you could also use XML, etc.) and tell MySQL to connect (or whatever database you’re using). Finally, we’re setting PHP’s __autoload() method to autoload classes with the simple [ClassName].php naming system from anywhere in our include path.

Step 4: Routing to a controller

Alright, on to the meat.  This is going to be the longest step by far so hang on!  Next up, our RoutingController class (located in my install at /lib/RoutingController.php).
The route() function in our index.php class was actually covered in our last section:

$path = parse_url(
     (isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' .    // Scheme     
     $_SERVER['PHP_AUTH_USER'] . ':' .                          // User     
     $_SERVER['PHP_AUTH_PW'] . '@' .   // Password     
     $_SERVER['HTTP_HOST'] .                                    // Hostname     
     $_SERVER['REQUEST_URI']);                                  // Path and query string
$temp = explode("/", substr($path['path'], 1));
$controller = strtolower((@$temp[0]) ? $temp[0] : "welcome");
$module = strtolower((@$temp[1]) ? $temp[1] : "index");

if(!file_exists(CONTROLLER_DIR . "{$controller}Controller.php")) {     
     $controller = "Error";
     $module = "index";
}

if(!method_exists("{$controller}Controller", "{$module}Handler")) {
     $controller = "error";
     $module = "index";
}

$class = $controller . "Controller";
$controller = new $class($controller, $module);
$method = "{$module}Handler";
$controller->$method();
$controller->render();

Basically, we’re tearing apart the URL and redirecting control to a Controller.  So let’s take a look at the Controller base class, from which all of our other controllers will inherit:

<?php

class Controller {
        private $Template;
        protected $vars = array();
        private $Controller;
        private $Module;

        function Controller($controller, $module) {
                $this->Template = new Template;

                $this->Controller = $controller;
                $this->Module = $module;
        }

        function get($var) {
                if(isset($_GET[$var]))
                        return($_GET[$var]);
                return(false);

        }

        function post($var) {
                if(isset($_POST[$var]))
                        return($_POST[$var]);
                return(false);

        }

        function cookie($var) {
                if(isset($_COOKIE[$var]))
                        return($_COOKIE[$var]);

                return(false);
        }

        function setLayout($layout) {
                $this->Template->setLayout($layout);
        }

        function redirect($controller, $module) {
                if(strcmp($module, "index"))
                        header("Location: /$controller/$module");
                else
                        header("Location: /$controller");
        }

        function render() {
                // Set our template variables first
                foreach($this->vars as $key => $value)
                        $this->Template->set($key, $value);

                // Render
                $this->Controller = strtolower(substr($this->Controller, 0, 1)) . substr($this->Controller, 1);
                $this->Template->render(VIEW_DIR . "/{$this->Controller}/{$this->Module}.php");
        }
}

Again, we’ll walk through it bit by bit (function by function in this case).  We start off with our constructor, which saves the controller and module that are being called and initializes our template class (which we’ll look at in the another part).  Next we’ve defined wrapper classes around our get, post and cookie variables.  The reason for this is that while I’m not doing it here, you can preform various sanitizations on the values getting returned.  We define a wrapper function to make things a little easier for our users for the template in setLayout. The only real use for this is if we want to disable any page layout (again, more on this in our section on templating).  We also define a redirection function to redirect internally (adding support for external redirects will be left as an activity for you). Finally, we have created a render function which was called from our RoutingController which passes set variables into the template and then tells it which file will generate the content for the layout.

Whew! It’s a lot, I know, and I’ve tried to compress is as much as possible.  But keep on trekking, we’re almost there.

Now we’ve got URL re-writing and our basic routing system and controller base class set up.  So let’s look at an actual controller. We will define a basic “welcome” controller I will include with every default deployment of this system. This file will therefore be located at /controllers/welcomeController.php:

class welcomeController extends Controller {
        function indexHandler() {
                $model = new SomeModel;
                $this->vars['title'] = 'Welcome';
        }
}

This controller illustrates several things:

  1. The class name and file name must be the same.  This is because in our __autoload call in our initialization file, we told it to look for [controller]Controller in the file with the same name.
  2. Our module name must be in a function called [module]Handler.  As I’ve said throughout this entire series, this is simply a design decision on my part and is located in the RoutingController::route function.
  3. The default module, as defined in the same route function is “index” so if there is no module (ie. domain.com/welcome) it will be sent to the function indexHandler().
  4. Variables passed in to our template will be located in $this->vars.  These variables will be passed into our layout and our controller/module template.  In this case, we’re passing the page title in as a variable called “title”, therefore we set $this->vars[‘title’].

Step 5: Models and Views

This will be a short section, mostly because Models will be covered in more detail when we look at database abstraction and we’re going to cover views when we look at our template system.  But let’s take a quick peek nonetheless:

Models are our custom classes.  So users, sessions, database records, etc.  All of our models reside in our /models directory.  Let’s take a look at our default model (for fun, defined at /models/Default.php):

class Default extends MySQLObject {
}

As you can see, we’re creating a Default class that extends our MySQLObject base class to be discussed later. Simple as that!

Next let’s take a quick look at a view.  The corresponding view for our welcomeController::indexHandler function can be found at /views/welcome/index.php.  This can include an entire HTML file, but as you’ll see later, our template class wraps whatever is in this file in a layout that is standard to all views.  So let’s take a look:

<h2><?php echo $tpl_vars[‘title’]; ?></h2>

This is the default welcome view file for each deployment.  Edit away!

Like I said, super simple.  However, you can see our template variable being used!

Step 6: Keep going!

This entire process has been trial and error for me, and as I said in the last section, I am by no means an expert in these subject.  But I haven’t seen a good guide on doing this sort of stuff yourself and I hope that you can take what I’ve presented here and keep going and create new and even more wonderful things.

In our next sections, we’ll cover database abstraction and templating!

6 Responses

  • waywardspooky

    Ummmm, where’s the rest of this tutorial. Without it everything you just described is pretty much useless.

    • Ajala John Temi

      This is about the tenth MVC tutorial that is not complete. I probably will just do a tutorial myself. :P

      • Hey @ajalajohntemi:disqus
        Right, only 2 parts to this tut. and 5 years old as well. Ping me if you want to contribute and finish this tutorial on HTMLCenter. We pay good rates for quality tutorial posts ;)

  • TuxLyn

    So far so good, but yeah please contenue :-)

    • TuxLyn, this tutorial is little bit old, actually 5 years old :) I hope we can add another piece to it some time in the future. Give us a shout if you have any suggestions

  • Cena

    sorry, how about session ? does it place in controller class ? or need to start above framework near Routing ?