Pages

Categories

A lightweight and flexible front controller for PHP 5

Apparently the front controller is a commonly misunderstood design pattern so I figured I’d throw out some thoughts to the masses.

To put it simply, a Front Controller is a gateway to an application. All requests hit the front controller leaving it to decide what to do next. Typically a front controller will set up the session, connect to the database and delegate requests to numerous page controllers (or action controllers). Everything that happens here is program flow logic.

You could create a really basic front controller by putting a simply switch() statement inside an index.php file:

<?php

session_start();

$page = !empty($_GET["page"]) ? $_GET["page"] : "home";
switch ($page) {
  case "home":
    include "pages/home.php";
    break;
  case "guestbook":
    include "pages/guestbook.php";
    break;
}

Ths sort of procedural programming works, but I’ll try to convince you of the benefit of creating even a simple website in an object oriented fashion. The code we’ll produce to power an extremely scalable and flexible controller architecture should take no more than a couple of hundred lines. Of course, once you have the groundwork down it’s up to you how much fluff you begin to add to make it even more magical ;)

The object oriented approach

Typically a front controller will be a class with a known method name invoked to “launch” the application. We’ll call this class “FrontController” (can’t think why!) and we’ll name it’s dispatcher method “dispatch()”. Let’s start basic and add complexity as we progress. Usually an application powered by the Front Controller pattern will appear to have virtually no logic in its index file.

Some groundwork

index.php

<?php

define("PAGE_DIR", dirname(__FILE__) . "/pages");
require_once "FrontController.php";
FrontController::createInstance()->dispatch();

FrontController.php

<?php

class FrontController {
  public static function createInstance() {
    if (!defined("PAGE_DIR")) {
      exit("Critical error: Cannot proceed without PAGE_DIR.");
    }
    $instance = new self();
    return $instance;
  }
  public function dispatch() {
    $page = !empty($_GET["page"]) ? $_GET["page"] : "home";
    $action = !empty($_GET["action"]) ? $_GET["action"] : "index";
    //e.g. HomeActions
    $class = ucfirst($page) . "Actions";
    //e.g. pages/home/HomeActions.php
    $file = PAGE_DIR . "/" . $page . "/" . $class . ".php";
    if (!is_file($file)) {
      exit("Page not found");
    }
    require_once $file;
    $actionMethod = "do" . ucfirst($action);
    $controller = new $class();
    if (!method_exists($controller, $actionMethod)) {
      exit("Page not found");
    }
    //e.g. $controller->doIndex();
    $controller->$actionMethod();
    exit(0);
  }
}

pages/guestbook/GuestbookActions.php

<?php

class GuestbookActions {
  public function doIndex() {
    echo "Index action called…";
  }
  public function doCreatePost() {
    echo "CreatePost action called…";
  }
}

Now open up your browser and point it to http://localhost/index.php?page=guestbook&action=index

You should see “Index action called…”.

Change the URL to http://localhost/index.php?page=guestboook&action=createPost

Nice eh?

Ok, well we can do better than this. I mean, we don’t really want to be outputting stuff in our controller classes. That sort of logic should be inside a template. For our guestbook above we’ve got “index” and “createPost” actions. Lets’ make some templates associated with each of those actions.

Introducing the view

We’ll make some templates at pages/guestbook/indexView.php and pages/guestbook/createPostView.php. This is painfully straightfoward really.

pages/guestbook/indexView.php

Index action called…

pages/guestbook/createPostView.php

CreatePost action called…

Now lets get these files included from our front controller. For now I’m just going to modify the dispatch() method a little bit.

<?php

class FrontController {
  // … snip … //
  public function dispatch() {
    $page = !empty($_GET["page"]) ? $_GET["page"] : "home";
    $action = !empty($_GET["action"]) ? $_GET["action"] : "index";
    //e.g. HomeActions
    $class = ucfirst($page) . "Actions";
    //e.g. pages/home/HomeActions.php
    $file = PAGE_DIR . "/" . $page . "/" . $class . ".php";
    if (!is_file($file)) {
      exit("Page not found");
    }
    require_once $file;
    $actionMethod = "do" . ucfirst($action);
    $controller = new $class();
    if (!method_exists($controller, $actionMethod)) {
      exit("Page not found");
    }
    //e.g. $controller->doIndex();
    $controller->$actionMethod();
    $view = PAGE_DIR . "/" . $page . "/" . $action . "View.php";
    if (!is_file($view)) {
      exit("Page not found");
    }
    include $view;
    exit(0);
  }
}

Now lets just remove that yucky output from the action controller ;)

<?php

class GuestbookActions {
  public function doIndex() {
  }
  public function doCreatePost() {
  }
}

You should basically have the same results as before when you accessed those earlier URLs in your browser, except now our presentation logic is in the right place.

As it stands all we’ve done is introduced some naming conventions and gotten our website to run a contoller method before it includes our usual file. So how do we get some variables into the template? I’m going to add some logic to the GuestbookActions class which I’d like to be available to other action controllers. Time to refactor a little.

Passing data to the view

The first change I’d ike to make is to ensure all action controller classes can share some common features. I’ll create an ActionController class and I’ll make it abstract because it should only extended, not instantiated. I’ll move the responsibility for running it’s own action into this class too. This should clean up our front controller a little.

ActionController.php

<?php

abstract class ActionController {
  protected $name;
  protected $viewData = array();
  public function setName($name) {
    $this->name = $name;
  }
  public function getName() {
    return $this->name;
  }
  public function setVar($key, $value) {
    $this->viewData[$key] = $value;
  }
  public function getVar($key) {
    if (array_key_exists($key, $this->viewData)) {
      return $this->viewData[$key];
    }
  }
  public function dispatchAction($action) {
    $actionMethod = "do" . ucfirst($action);
    if (!method_exists($this, $actionMethod)) {
      exit("Page not found");
    }
    $this->$actionMethod();
    $this->displayView($action);
  }
  public function displayView($action) {
    if (!is_file(PAGE_DIR . "/" . $this->getName() . "/" . $action . "View.php")) {
      exit("Page not found");
    }
    //Create variables for the template
    foreach ($this->viewData as $key => $value) {
      $$key = $value;
    }
    include PAGE_DIR . "/" . $this->getName() . "/" . $action . "View.php";
    exit(0);
  }
}

Now we can tidy up the dispatch() method in our front controller.

<?php

require_once dirname(__FILE__) . "/ActionController.php";

class FrontController {
  //… snip …
  public function dispatch() {
    $page = !empty($_GET["page"]) ? $_GET["page"] : "home";
    $action = !empty($_GET["action"]) ? $_GET["action"] : "index";
    //e.g. HomeActions
    $class = ucfirst($page) . "Actions";
    //e.g. pages/home/HomeActions.php
    $file = PAGE_DIR . "/" . $page . "/" . $class . ".php";
    if (!is_file($file)) {
      exit("Page not found");
    }
    require_once $file;
    $controller = new $class();
    $controller->setName($page);
    $controller->dispatchAction($action);
  }
}

Now if we make our GuestbookActions class extend the ActionController class we can get it to pass data into the template ;)

<?php

class GuestbookActions extends ActionController {
  public function doIndex() {
    $this->setVar("hello", "World!");
  }
  public function doCreatePost() {
    $this->setVar("hello", "Universe!");
  }
}

Cross your fingers and hope it works.

pages/guestbook/indexView.php

Index action called. The controller said hello to <?php echo $hello ?>

pages/guestbook/createPostView.php

CreatePost action called. The controller said hello to <?php echo $hello ?>

Did it work? :)

Ok, great, for me personally I’d get sick of typing setVar() and getVar() everytime I want to make something available to the view so I’m going to add in a controversial setter/getter system. We’re using PHP 5 right?

The __set() and __get() methods are invoked whenever you try to access a property that doesn’t exist.

ActionController.php

<?php

abstract class ActionController {
  // … snip …
  public function __set($key, $value)  {
    $this->setVar($key, $value);
  }
  public function __get($key) {
    return $this->getVar($key);
  }
}

Now in our GuestbookActions class we can simply pass data to the view by assigning it to non-existent properties.

class GuestbookActions extends ActionController {
  public function doIndex() {
    $this->hello = "World!";
  }
  public function doCreatePost() {
    $this->hello = "Universe!";
  }
}

This is great, it works nicely and we’ve got a clean separation of presentation logic and controller logic. One thing that’s missing though is a common featureset available to all components which form part of the controller layer. The controller layer will usually contain methods for getting a handle on the database, getting the request & response objects and getting the session. I’m not going to write all the code out for those components because I’m sure you can use your imagination so I’m just going to add some pseudo code at this point.

All controllers belong to the controller layer

Lets make an abstract class called quite simply “Controller”. Here we can add anything which needs to be available everywhere in the controller layer.

Controller.php

<?php

abstract class Controller {
  public function getRequest() {
  }
  public function getResponse() {
  }
  public function getSession() {
    //I tend to prefer $this->getRequest()->getSession(), but whatever!
  }
}

Just change FrontController and ActionController to extend this class and we’ve provided a complete new set of features everywhere in the controller layer.

FrontController.php

<?php

require_once dirname(__FILE__) . "/Controller.php";
require_once dirname(__FILE__) . "/ActionController.php";

class FrontController extends Controller {
  // … snip …
}

ActionController.php

<?php

abstract class ActionController extends Controller {
  // … snip …
}

There’s just one last thing I’d like to to introduce which sets this sort of architecture apart from procedural programming. Wouldn’t it be nice if t wasn’t only the front controller who could dispatch actions at runtime? I mean, it would be pretty cool if we could forward our request from one action controller to another right?

Enhancing program flow at the controller level

Imagine in our guestbook we block users temporarily if they submit more than say 2 posts in a minute. We could redirect away from our “createPost” action and send them to a “tryLater” action, but then the browser would have taken them away from the page. No, we can infact make this far more streamlined and “forward()” from one action to another.

We just created our Controller class, so why not stick a forward() method in there and rather than have FrontController locate and dispatch the action, we’ll get it to forward() to it. Effectively I’m just refactoring code here.

Controller.php

<?php

abstract class Controller {
  public function getRequest() {
  }
  public function getResponse() {
  }
  public function getSession() {
  }
  public function forward($page, $action) {
    //e.g. HomeActions
    $class = ucfirst($page) . "Actions";
    //e.g. pages/home/HomeActions.php
    $file = PAGE_DIR . "/" . $page . "/" . $class . ".php";
    if (!is_file($file)) {
      exit("Page not found");
    }
    require_once $file;
    $controller = new $class();
    $controller->setName($page);
    $controller->dispatchAction($action);
    exit(0);
  }
}

I’ve just moved the code and added a call to exit(), but now see what we can achieve.

Simplfy our Front Controller:

<?php

require_once dirname(__FILE__) . "/ActionController.php";

class FrontController {
  //… snip …
  public function dispatch() {
    $page = !empty($_GET["page"]) ? $_GET["page"] : "home";
    $action = !empty($_GET["action"]) ? $_GET["action"] : "index";
    $this->forward($page, $action);
  }
}

I’m not going to write the logic for tracking how many times the user has posted in the past minute I’ll just show that you can direct the flow of the application by invoking forward() from the action controller classes.

<?php

class GuestbookActions extends ActionController {
  protected $maxPostsPerMin = 2;
  public function doIndex() {
    $this->hello = "World!";
  }
  public function doCreatePost() {
    $posts_this_minute = 3; //yeah whatever
    if ($posts_this_minute > $this->maxPostsPerMin)  {
      $this->forward("guestbook", "tryLater");
    }
    $this->hello = "Universe!"; //Never executes this
  }
  public function doTryLater() {
    $this->max = $this->maxPostsPerMin;
  }
}

Add a new template:
pages/guestbook/tryLaterView.php

Sorry you‘ve submitted too many posts in the last minute.
Maximum number of posts per minute is <?php echo $max ?>.

Beautiful! This sort of layout provides an immense amount of flexibility for creating highly scalable websites. Of course, this is only the groundwork but it should be enough to give you an understanding of the front controller design pattern (and aspects of MVC). Go away and play with the code. Starting by writing the code for getRequest() and getResponse() would be an idea ;)

This entry was posted on Sunday, August 12th, 2007 at 1:13 pm and is filed under PHP. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

19 Responses to “A lightweight and flexible front controller for PHP 5”

  • Robert Gonzalez [ 13Aug07]

    Hot damn Chris, this is so far the best tutorial on implementing controllers and control to view actions that I have ran across! I think this is a great starting point for anyone that is interested in MVC or learning how to use Front/Action controllers in PHP5.

    Awesome dude. This thing rocks!

  • One man’s voice » Blog Archive » Chris Corbyn’s Controller and View tutorial [ 13Aug07]

    […] has an excellent tutorial on challenge and response in the PHPDN forums. Last night I ran into one such tutorial by Chris Corbyn, author of the freaking outright awesome Swiftmailer […]

  • d11wtq [ 13Aug07]

    Hey dude, glad you like. I fixed the $action typo now too :)

  • Luke Visinoni [ 14Aug07]

    Nice write-up. This makes me want to write some PHP again… it’s been so long since I wrote any kind of OOP PHP, since, like I mentioned, I’ve been living and breathing in Python lately.

    I only have one question here. Why would you use something like FrontController::createInstance()->dispatch() when you could have just as easily done something like $front = new FrontController();
    $front->dispatch();

    It seems that all you really did was recreate a constructor, am I missing something?

  • d11wtq [ 14Aug07]

    You’re right, it could just of easily have been done through the normal constructor.

  • Bram.us » A lightweight and flexible front controller for PHP 5 [ 16Aug07]

    […] writeup by Chris Corbyn (author of Swift Mailer): “Apparently the front controller is a commonly misunderstood design pattern so I figured I’d throw ….” Spread the […]

  • Stalski! [ 22Aug07]

    Awesome shit dude! One of the nicest posts I’ve read on MVC application. But what I like the most is that you put in the magic functions as well. way to go

  • chris [ 28Aug07]

    FYI, someone mentioned over at DevNetwork that I haven’t got any validation in my code to stop people loading files from /etc/passwd or /etc/shadow etc. A simple preg_replace() would prevent that:

    $page = preg_replace("/[^a-zA-Z0-9_]+/", "", $page);
    $action = preg_replace("/[^a-zA-Z0-9_]+/", "", $action);

    :)

  • dimex [ 30Sep07]

    Hello,
    great post, thank you for sharing.
    1.How would you handle post data, like from a form?
    2. what about function arguments? if you need to pass two arguments to a function, wouldn’t you need to get them from the querystring as well?

  • Daniel [ 02Oct07]

    Great tutorial!

    is it possible you could extened it to include the composite pattern?

  • d11wtq [ 10Oct07]

    Hi guys, sorry for the delay in responding. I moved out to Australia a week and a half ago and am now a SitePoint employee.

    With respect to handling POST (and GET for that matter if we’re being pedantic ;)) you really want to create a Request class. Pretty simple really:

    $x = $req->getParameter(”something”);
    $y = $req->getParameter(”thing”, Request::POST);
    $req->hasParameter(”foo”);
    $req->setError(”some_field”, “Field not filled in”);
    $req->getRequestURI();

    etc etc etc…

    Take a look at Java’s HttpServletRequest class for inspiration, or symfony’s sfRequest class.

    With respect to the composite design pattern I’m assuming you’re referring more specifically to a composite view? That’s a View layer topic and something which warrants a completely separate article. I guess I could write an article on the basics of the composite view when I get some time :)

    Thanks for the feedback guys.

  • Daniel [ 23Nov07]

    This is my front controller:

    Every action gets dispatch here and not in page controllers.

    I will post my composite page controller aswell if you like. Although I would really like to see how you do it.

    directory = $directory;
    }

    /**
    *
    * Sets the controller default action.
    *
    * @param string $class The class to call.
    * @param string $method The method to dispatch.
    *
    * @return void
    *
    */
    function setDefault($class, $method) {
    $this->default = $this->forward($class, $method);
    }

    /**
    *
    * Sets the controller error action.
    *
    * @param string $class The class to call.
    * @param string $method The method to dispatch.
    *
    * @return void
    *
    */
    function setError($class, $method) {
    $this->error = $this->forward($class, $method);
    }

    /**
    *
    * Sets the controller error controller action.
    *
    * @param string $class The class to call.
    * @param string $method The method to dispatch.
    *
    * @return void
    *
    */
    function addPreAction($class, $method) {
    $this->pre_action[] = $this->forward($class, $method);
    }

    /**
    *
    * Dispatches the pre actions and page controller class.
    *
    * @param object $request The request object.
    *
    * @return void
    *
    */
    function dispatch(&$request) {
    $action = $this->requestHandler($request);

    while ($action) {
    foreach ($this->pre_action as $pre_action) {
    $result = $this->execute($pre_action);

    if ($result) {
    $action = $result;

    break;
    }
    }

    $action = $this->execute($action);
    }
    }

    /**
    *
    * Executes a controller class.
    *
    * @param array $action The action to be executed.
    *
    * @return array $action Returns another action so the controller can be forwarded. If there is no foward action NULL is returned.
    *
    */
    function execute($action) {
    $file = $this->directory . basename($action[’class’]) . ‘.php’;

    if (file_exists($file)) {
    include_once($file);

    $controller = ‘Controller’ . preg_replace(’/[^a-zZ-Z0-9]/’, NULL, $action[’class’]);

    $class = new $controller($this->registry);

    if (method_exists($class, $action[’method’])) {
    $action = $class->{$action[’method’]}($this->registry);
    } else {
    $action = $this->error;

    $this->error = NULL;
    }
    } else {
    $action = $this->error;

    $this->error = NULL;
    }

    return $action;
    }

    /**
    *
    * Handles the request and creates an action ready for dispatch.
    *
    * @param object $request The request object to is used to build the action to be executed.
    *
    * @return array $action Returns an action so the controller can be forwarded.
    *
    */
    function requestHandler(&$request) {
    if ($request->has(’controller’)) {
    $class = $request->get(’controller’);

    if ($request->has(’action’)) {
    $method = $request->get(’action’) . ‘Action’;
    } else {
    $method = ‘defaultAction’;
    }

    return $this->forward($class, $method);
    } else {
    return $this->default;
    }
    }
    }
    ?>

  • Daniel [ 28Nov07]

    Hi,

    Can you please email me some eample code of how you setup you composite views?

  • chris [ 01Dec07]

    I’ll write a blog post on composite views. It’s a wildly varying topic which ranges from composing each view manually (with include() for example), to creating a “layout” template and associating a page template with each controller, to going all out and providing a sub-controller/slot style view (generally a bad idea).

    I’m pretty busy right now with loads of other stuff but it’s on my todo list ;)

  • Risto [ 24Dec07]

    Best article ever made of controllers! Is this code free to use and modify?

  • Daniel [ 22Jan08]

    Can you please add a tutoral on your composite view pattern?

  • Jan Palencar [ 02Feb08]

    Hi Chris,
    i’m really happy that i’ve run across this site.
    I read an article in some PHP documentation, that’s were i got you web from.
    Thanks for your work,
    nice that you are sharing with us.

    i can say it’s a high quality code, since i coded a year or two (pro) in Java.
    can see nice OO techniques.

    see ya.

  • Anjanesh [ 24Mar08]

    Im assuming the reason you’re having
    protected $name; under abstract class ActionController is because __CLASS__ will return ActionController instead of GuestbookActions ?
    Well, since PHP 5.3 we should be able to get GuestbookActions.

  • WishCow [ 31Mar08]

    Great article!
    Small note, instead of

    foreach ($this->viewData as $key => $value) {
    $$key = $value;
    }

    You can do:

    extract($this->viewData);

Leave a Reply







XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>