Zend Framework Front Controller

Zend have finally released a preview of their new framework at http://framework.zend.com/.

I've started having a play and their implementation of a Front Controller is quite similar to what I've been playing with. This isn't a total suprise as there aren't that many ways to do a Front Controller :)

The documentation on the Zend/Controller directory is completely non existant, so I've worked out the basics by reading the source. Fortunately, the source isn't too hard to read!

To get something going, I set up my directories like this:

htdocs/
/lib/
/lib/Zend.php
/lib/Zend/

/wwwroot
/wwwroot/.htaccess
/wwwroot/index.php
/wwwroot/controllers
/wwwroot/controllers/IndexController.php
/wwwroot/controllers/TestController.php

.htaccess turns on mod_rewrite to redirect every url to index.php (if the url doesn't point to an existing file or directory):

RewriteEngine on

RewriteOptions Inherit
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule  (.*) index.php?apache_path=$1 [L,QSA]

wwwroot/index.php sets up the Front Controller...

< ?php
$path = '../lib/';
set_include_path(get_include_path() . PATH_SEPARATOR . $path);

include "../lib/Zend.php";

Zend::loadClass('Zend_Controller_Dispatcher');
Zend::loadClass('Zend_Controller_Router');
Zend::loadClass('Zend_Controller_Front');

$router = new Zend_Controller_Router();

$dispatcher = new Zend_Controller_Dispatcher();
$dispatcher->setControllerDirectory('./controllers');

$frontContoller = Zend_Controller_Front::getInstance();
$frontContoller->setControllerDirectory('./controllers');
$frontContoller->setRouter($router);
$frontContoller->setDispatcher($dispatcher);

$frontContoller->dispatch();
?>

Note that Zend::loadClass() and Zend::loadInterface() assume that the Zend/ directory is on the path...

You also need a controller, so I created wwwroot/controllers/IndexController.php:

< ?php
class IndexController extends Zend_Controller_Action
{
function index()
{
echo 'in index()';
}
function test()
{
echo 'in test()';
}
function noRoute()
{
echo 'in noRoute()';
}
}

Not exactly complicated, but it tells me what's going on.

Very quickly, I discovered that the Zend_Controller_Router assumes that my index.php lives in my webserver's document root. That isn't the case on my test machine, so I decided to write my own router. I chose to put this in lib/rka/Router.php and called the class RKA_Router so that I could take advantage of one of the Zend::loadClass();

Being lazy, I just cloned Zend_Controller_Router and altered it a little. This is my change:
lib/rka/Router.php:

< ?php

/** Zend_Controller_Router_Interface */
require_once 'Zend/Controller/Router/Interface.php';

/** Zend_Controller_Dispatcher_Interface */
require_once 'Zend/Controller/Dispatcher/Interface.php';

/** Zend_Controller_Router_Exception */
require_once 'Zend/Controller/Router/Exception.php';

/** Zend_Controller_Dispatcher_Action */
require_once 'Zend/Controller/Dispatcher/Action.php';

class RKA_Router implements Zend_Controller_Router_Interface
{

public function route(Zend_Controller_Dispatcher_Interface $dispatcher)
{
// RKA: what's the path to where we are?
$path_to_index = dirname($_SERVER['SCRIPT_NAME']);

// RKA: remove $path_to_index from $_SERVER['REQUEST_URI']
$path = str_replace($path_to_index, '', $_SERVER['REQUEST_URI']);
if (strstr($path, '?')) {
$path = substr($path, 0, strpos($path, '?'));
}
$path = explode('/', trim($path, '/'));

/**
* The controller is always the first piece of the URI, and
* the action is always the second:
*
* http://zend.com/controller-name/action-name/
*/
$controller = $path[0];
$action     = isset($path[1]) ? $path[1] : null;

/**
* If no controller has been set, IndexController::index()
* will be used.
*/
if (!strlen($controller)) {
$controller = 'index';
$action = 'index';
}

/**
* Any optional parameters after the action are stored in
* an array of key/value pairs:
*
* http://www.zend.com/controller-name/action-name/param-1/3/param-2/7
*
* $params = array(2) {
*              ["param-1"]=> string(1) "3"
*              ["param-2"]=> string(1) "7"
* }
*/
$params = array();
for ($i=2; $iisDispatchable($actionObj)) {
/**
* @todo error handling for 404's
*/
throw new Zend_Controller_Router_Exception('Request could not be mapped to a route.');
} else {
return $actionObj;
}
}
}
?>

The only bit I changed are commented with "//RKA". Essentially, I work out the path to the index.php and then remove it from $_SERVER['REQUEST_URI'] so that I can have urls of the form http://localhost/test/wwwroot/{controller}/{action} rather than http://localhost/{controller}/{action}

I had to modify index.php by changing:

Zend::loadClass('Zend_Controller_Router');

to

Zend::loadClass('RKA_Router');

and obviously:

$router = new Zend_Controller_Router();

to

$router = new RKA_Router();

And that's about it. The Zend Framework's front controller works fine. Any public non-static function within a controller class is considered an action. So http://localhost/index/test will call the IndexController::test();

Next up is to work out how to tie this in with the Zend_View class!

(Update: WordPress is doing something funky with the double quotation marks in the PHP code, they aren't really escaped!)

If you would like to comment on this article, please ping me on twitter.
If your response won't fit into 140 characters, write a blog post and then ping me!