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:
$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
FrontController.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
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
pages/guestbook/createPostView.php
Now lets get these files included from our front controller. For now I’m just going to modify the dispatch() method a little bit.
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 ;)
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
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.
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 ;)
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
pages/guestbook/createPostView.php
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
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.
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
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
ActionController.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
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:
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.
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
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 ;)
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:
$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.
}
/**
*
* 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
$$key = $value;
}
You can do: