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($pathstrlen($base));
        if (strstr($path'?')) {
            $path substr($path0strpos($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!

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!