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!)

16 Responses to “Zend Framework Front Controller”

  1. 1 Rob...

    I really have to get a better template for doing code!

  2. 2 Gom Jabbar

    You might also want to have a look at a syntax highlighter plugin for WordPress. I recently found this one:

    http://matthew.delmarters.com/weblog/visual_syntax/

    Haven't really checked it yet, though, so I can't tell you whether or not it's any good.

  3. 3 Nico Edtinger

    I guess Zend::load_class() is meant to be used in an __autoload function. This would it also make easier to change the controlling classes like your router.

  4. 4 Rob...

    @Gom:
    Ooh! I'll have a look. Thanks!

    @Nico:
    __autoload()… that's a good idea. I'll try that as it'll save having to keep track of stuff.

    Another thing I've noticed is that that Zend::loadFile() calls Zend::isReadable() which does an fopen() followed by an fclose(). I wonder if there's any performance issues with doing that for every file you want to include() ?

  5. 5 soenke

    Nice notes, thx. The controller looks very interresting.

    Btw: have a look at css overflow directive :)

  6. 6 Rob...

    Thanks soenke! Can you tell that I'm a programmer and not a designer! It does show up that this template doesn't have enough "width" to it though…

  7. 7 Rob...

    Autoload doesn't work with Zend::loadClass() because it calls class_exists() to check that the class isn't already loaded:

    if (class_exists($class)) {
    return;
    }

    This results in the __autoload() being called which calls Zend::loadFile() which then gets confused :)

    Workaround is to modify to:
    if(class_exists($class, false)) {
    return;
    }

  8. 8 Prasanna

    Have you tried zend_search? Some sample code would be very helpful.
    thanks.

  9. 9 Rob...

    Zend_Search is definitely on my list of things to look at, but I need a working site first :)

  10. 10 Jamie

    Am i looking at this correctly? Do i need a contoller function for every static link i place in a page. For example: several non-dynamic informational pages would each need it's own controller entry for a link to work. Seems like a lot of code to replace a href but i don't see a way around it with the mod_rewrite.

  11. 11 Rob...

    You are looking at it wrong. The rewrite rule on this page will serve a file if it exists, but if it doesn't, then it will serve index.php.

    Incidentally, the rule in the ZF manual is different…

  12. 12 Jamie

    Ok, i added an RewriteCond to exclude a directory and added all static pages in that directory which worked fine. Thanks.

  13. 13 zibin

    I am looking at excluding a directory using RewriteCond. Can't seem to figure out.

    Hope someone can lend a hand. thanks

  14. 14 Amit Shah

    How could we call an action of one controller into another controller?

    Thanks

  15. 15 Rob...

    zibin,

    try:

    RewriteCont -d

    Regards,

    Rob…

  16. 16 Rob...

    Amit,

    You should let the dispatcher do it - look up the Action Stack front controller plug in.

    Regards,

    Rob…

The views expressed in these comments are not the views of the publisher. However, we believe in the rights of others to express their legitimate views and concerns. Any legitimate complaint emailed to rob@akrabat.com will be seriously considered and the post reviewed as desirable and necessary.

Leave a Reply

Pre order