Test if request is really dis­patch­able in Zend Framework

Some­times you need to check if request is dis­patch­able. The most com­mon place where you can meet this need is in con­trol­ler plug-in. I have met that I need to test if request is dis­patch­able when I have imple­men­ted my App_Controller_Plugin_Acl for ACL check­ing based on ZF pro­posal. So ACL test is run before request will be dis­patched. Of course I don’t want to run ACL checks for requests that are not dis­patch­able. I relay on stand­ard dispatcher’s Zend_Controller_Disptcher_Interface::isDispatchable() and that was my mis­take — as I spent hours try­ing to under­stand what I’m doing wrong. Yes! The prob­lem was that isDispatchable() doesn’t care about actions — it tests con­trol­ler exist­ence only. But for­tu­nately it still can be eas­ily tested…

As I said before, I was try­ing to use the same way I saw in proposal:

    $dispatcher = Zend_Controller_Front::getInstance()->getDispatcher();
    if (!$dispatcher->isDispatchable($request)) {
        return;
    }

But this code will cause an unpre­dict­able res­ult. As it checks only a con­trol­ler exist­ence and don’t care about action. You’ll fig­ure it out if you’ll look at this method in the sources:

// Zend/Controller/Dispatcher/Standard.php
// ...
    public function isDispatchable(Zend_Controller_Request_Abstract $request)
    {
        $className = $this->getControllerClass($request);
        if (!$className) {
            return false;
        }

        if (class_exists($className, false)) {
            return true;
        }

        $fileSpec    = $this->classToFilename($className);
        $dispatchDir = $this->getDispatchDirectory();
        $test        = $dispatchDir . DIRECTORY_SEPARATOR . $fileSpec;
        return Zend_Loader::isReadable($test);
    }
// ...

After quick-time googling and annoy­ing my col­league Kir­ill AKA dhampik, who showed me a post with the same prob­lem, I have rewrit­ten method found in that solu­tion, so it’s now clean and simple. For this pur­pose I have defined my abstract class App_Controller_Plugin_Abstract:

< ?php

/**
 * Zend_Controller_Plugin_Abstract
 */
require_once 'Zend/Controller/Plugin/Abstract.php';

class App_Controller_Plugin_Abstract extends Zend_Controller_Plugin_Abstract
{
    /**
     * Return whether a given request (module-controller-action) exists
     *
     * @param Zend_Controller_Request_Abstract $request Request to check
     * @return boolean Whether the action exists
     */
    protected function _actionExists(Zend_Controller_Request_Abstract $request)
    {
        $dispatcher = Zend_Controller_Front::getInstance()->getDispatcher();

        // Check controller
        if (!$dispatcher->isDispatchable($request)) {
            return false;
        }

        $class  = $dispatcher->loadClass($dispatcher->getControllerClass($request));
        $method = $dispatcher->formatActionName($request->getActionName());

        return is_callable(array($class, $method));
    }
}

Usage of this class and method is very easy. Let’s say you are mak­ing your plug-in for deal­ing with ACL on pre-dispatch event:

< ?php

/**
 * App_Controller_Plugin_Abstract
 */
require_once 'App/Controller/Plugin/Abstract.php';

class App_Controller_Plugin_Acl extends App_Controller_Plugin_Abstract
{
    // ...

    public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
        if ( ! $this->_actionExists($request)) {
            return;
        }

        // ...
    }

    // ...
}

Changelog:
UPD[2010/02/24]:
Changed in_array($method, get_class_methods($class)) to is_callable(array($class, $method)) as it works little bit faster. So dummy test:

class Foo
{
    public function foo()
    {}
}

$class    = 'Foo';
$method_a = 'foo';
$method_b = 'moo';

$time_a = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    is_callable(array($class, $method_a));
    is_callable(array($class, $method_b));
}
$time_b = microtime(true);
echo 'is_callable()' . PHP_EOL;
echo 'Start: ' . $time_a . PHP_EOL;
echo 'Stop: ' . $time_b . PHP_EOL;
echo 'Diff: ' . ($time_b-$time_a) . PHP_EOL;

$time_a = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    in_array($method_a, get_class_methods($class));
    in_array($method_b, get_class_methods($class));
}
$time_b = microtime(true);
echo 'in_array()' . PHP_EOL;
echo 'Start: ' . $time_a . PHP_EOL;
echo 'Stop: ' . $time_b . PHP_EOL;
echo 'Diff: ' . ($time_b-$time_a) . PHP_EOL;

Will show res­ults as follows:

is_callable()
Start: 1267036706.61
Stop: 1267036716.43
Diff: 9.82274079323
in_array()
Start: 1267036716.43
Stop: 1267036729.76
Diff: 13.3308188915
  • del.icio.us
  • Google Bookmarks
  • Identi.ca
  • Twitter
  • Technorati
  • Digg
  • Slashdot
  • Facebook
  • MisterWong
  • Reddit
  • StumbleUpon
  • Mixx
  • HelloTxt
  • LinkedIn
  • PDF
  • email
  • Print
This entry was posted in Zend Framework and tagged , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>