Zend Framework: Router Again
I’ve updated Akrabat_Router to take advantage of a patch posted by Christopher Thompson on the fw-general list. I’ve also put in support for using traditional request variables so that I can have urls like http://localhost/zf_test/index.php?command=user&action=login etc. This is mainly for use with IIS as ISAPI_Rewrite costs money :)
The class now looks like this:
< ?php
/**
* This is a direct copy of Zend_Controller_Router altered
* to allow for the controller to be in a sub-directory of
* the web root.
*
* Includes a patch from Christopher Thompson
*/
/** 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_Token */
require_once 'Zend/Controller/Dispatcher/Token.php';
class Akrabat_Controller_Router implements Zend_Controller_Router_Interface
{
private $script_extension;
public function __construct($script_extension='.php')
{
$this->script_extension = $script_extension;
}
public function route(Zend_Controller_Dispatcher_Interface $dispatcher)
{
/**
* @todo Replace with Zend_Request object
*/
$path = $_SERVER['REQUEST_URI'];
if (stripos($path, $this->script_extension) !== FALSE) {
$base = $_SERVER['SCRIPT_NAME']; // using script name
} else {
$base = dirname($_SERVER['SCRIPT_NAME']); // using rewrite rules
}
$path = substr($path, strlen($base));
if (strstr($path, '?')) {
$path = substr($path, 0, strpos($path, '?'));
}
if(strlen($path) == 0)
{
// look for command line parameters where POST overides GET
$filterGet = zend::registry('filterGet'); /* @var $filterGet Akrabat_InputFilter */
$filterPost = zend::registry('filterPost'); /* @var $filterPost Zend_InputFilter */
$command = $filterPost->getAlphaWithDefault('command', $filterGet->getAlphaWithDefault('command'));
$action = $filterPost->getAlphaWithDefault('action', $filterGet->getAlphaWithDefault('action'));
$path[0] = $command;
$path[1] = $action;
}
else
{
$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; $i<sizeof ($path); $i=$i+2) {
$params[$path[$i]] = isset($path[$i+1]) ? $path[$i+1] : null;
}
$token = new Zend_Controller_Dispatcher_Token($controller, $action, $params);
if (!$dispatcher->isDispatchable($token)) {
/**
* @todo error handling for 404's
*/
throw new Zend_Controller_Router_Exception('Request could not be mapped to a route.');
} else {
return $token;
}
}
}
?>
The interesting stuff is at the top of route(). Note that the code assumes that your index.php has this code:
$filterPost = new Akrabat_InputFilter($_POST);
$filterGet = new Akrabat_InputFilter($_GET);
Zend::register('filterPost', $filterPost);
Zend::register('filterGet', $filterGet);
Also, I had to derive Akrabat_InputFilter from Zend_InputFilter to provide getXxxxWithDefault() functionality.
Akrabat_InputFilter looks like this:
< ?php
/** Zend_Controller_Router_Interface */
require_once 'Zend/InputFilter.php';
class Akrabat_InputFilter extends Zend_InputFilter
{
public function exists($key)
{
return isset($this->_source[$key]);
}
/**
* Returns only the alphabetic characters in value.
*
* @param mixed $key
* @return mixed
*/
public function getAlphaWithDefault($key, $default='')
{
if($this->exists($key))
{
return Zend_Filter::getAlpha($this->_source[$key]);
}
else
{
return $default;
}
}
/**
* Returns only the digits in value. This differs from getInt().
*
* @param mixed $key
* @return mixed
*/
public function getDigitsWithDefault($key, $default=0)
{
if($this->exists($key))
{
return Zend_Filter::getDigits($this->_source[$key]);
}
else
{
return (int)$default;
}
}
// more getXxxxWithDefault() as required
}
I’m getting quite a collection of Akrabat_ overrides for Zend_ classes now. I fully expect that most of them will be redundant by 1.0!
Looks like I've got syntax highlighting working! I've bascially tweaked the plugin from http://matthew.delmarters.com/weblog/visual_syntax/.
Just out of interest, what editor do you use for PHP?
Zend Studio 5 mainly because of it's autocomplete system. I also use Emacs and UltraEdit. At work, we use a mixture of Zend Studio and Eclipse with TruStudio's plugin.
ISAPI_Rewrite Lite is free, and I have it installed on my system. One drawback (aside from not having 1:1 compatability with Rewrite rules) is that the Lite version only allows you to have one httpd.ini file to specify rules. So if you are developing more than just something in one directory you need to write conditions to have the rule(s) not apply to those directories. If anyone is interested I can post the httpd.ini file that I made that is basically the standard .htaccess file used with Zend Framework :)
Yeah, I've used ISAPI_Rewrite Lite but it applies to all websites on the server. This obviously isn't a problem in Windows XP Pro as you only have one site, but it is in W2K3 server…
(and I'm cheap!)
True that. I develop many sites on my localhost and I have to either condition them out of the rewrite rules or disable the rules altogether when working on something else. I also noticed that with ISAPI_Rewrite my server runs slow as sin.
It might be worth looking at IIS 7 since the architecture is supposidly changing to be more like apache, such as modules and the like…this may mean that there is a mod_rewrite builtin for IIS on its way, not sure though. I'd recommend checking out Channel9 for more info – Here's a link to a preview of IIS7 by the IIS Program Manager http://channel9.msdn.com/ShowPost.aspx?PostID=89540#89540. Personally I've used IIS as a development platform so cant really comment from that point of view.
That'd be nice. Though as my client has just got to Win2k3, I don't suppose they'll touch Vista server until 2009!
James, I'd be interested in seeing your ISAPI_Rewrite httpd.ini file.
Also, I'm trying to get Zend Framework working for a project I'm doing on IIS. Between redirection and REQUEST_URI not being available on IIS I can't seem to get it to work for me. Is anyone using Zend Framework on IIS?
I'm not using ZF on IIS yet. My current httpd.ini files for ISAPI_Rewrite looks like this:
[ISAPI_Rewrite]
RewriteRule (.*.jpg) $1 [L]
RewriteRule (.*.png) $1 [L]
RewriteRule (.*.gif) $1 [L]
RewriteRule (.*.css) $1 [L]
RewriteRule (.*.js) $1 [L]
RewriteRule (.*) /index.php
Thanks Rob. Just an FYI — if you're using ZF on IIS you'll obviously need to get around needing $_SERVER['REQUEST_URI']. If you're going to use ISAPI_Rewrite trying to get REQUEST_URI by using $_SERVER['PHP_SELF'] (like most IIS workarounds do it) won't work because what you need is what was requested, not the file that you're running in.
Anyway, it turns out that ISAPI_Rewrite sets a $_SERVER variable for you called HTTP_X_REWRITE_URL which is just what you need. I put this code at the top of Router.php and everything seems to be working:
if (!isset($_SERVER['REQUEST_URI'])) {
$path = $_SERVER['HTTP_X_REWRITE_URL'];
} else {
$path = $_SERVER['REQUEST_URI'];
}