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!)
I really have to get a better template for doing code!
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.
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.
@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() ?
Nice notes, thx. The controller looks very interresting.
Btw: have a look at css overflow directive :)
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…
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;
}
Have you tried zend_search? Some sample code would be very helpful.
thanks.
Zend_Search is definitely on my list of things to look at, but I need a working site first :)
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.
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…
Ok, i added an RewriteCond to exclude a directory and added all static pages in that directory which worked fine. Thanks.
I am looking at excluding a directory using RewriteCond. Can't seem to figure out.
Hope someone can lend a hand. thanks
How could we call an action of one controller into another controller?
Thanks
zibin,
try:
RewriteCont -d
Regards,
Rob…
Amit,
You should let the dispatcher do it – look up the Action Stack front controller plug in.
Regards,
Rob…