Automating “Implemented methods” for a web based API

In my leedsphp talk last week I mentioned making a developer (and consumers) life easier by automatically implementing the allow methods functionality that your API may expose (e.g. you call PUT on a URL that only allows GET or POST). I did have an example slide there showing how to implement but I thought I’d posted a bare bones controller here that shows how it works.

abstract class ApiController extends Zend_Rest_Controller
{
    /**
     * Default head action.
     *
     * @internal
     * @return void
     */
    public function headAction()
    {
        ob_start();

        $this->getAction();

        ob_get_clean();
    }

    /**
     * Default get action.
     *
     * @internal
     * @return void
     */
    public function getAction()
    {
        $this->_showAllowedMethods();
    }

    /**
     * Default post action.
     *
     * @internal
     * @return void
     */
    public function postAction()
    {
        $this->_showAllowedMethods();
    }

    /**
     * Default put action.
     *
     * @internal
     * @return void
     */
    public function putAction()
    {
        $this->_showAllowedMethods();
    }

    /**
     * Default delete action.
     *
     * @internal
     * @return void
     */
    public function deleteAction()
    {
        $this->_showAllowedMethods();
    }

    /**
     * Allowed methods output.
     *
     * @internal
     * @return void
     */
    private function _showAllowedMethods()
    {
        $methods = $this->implementedMethods();
        $methods = implode(',', $methods);
        $methods = strtoupper($methods);
        $this->_response->setHeader('Allow', $methods);

        $this->_forward('method-not-allowed', 'error', 'default'); # I have a methodNotAllowedAction on my ErrorController
							           # just for clarity - it does nothing other than headers
    }

    /**
     * Direct call to get implemented methods
     *
     * @internal
     * @return array
     */
    public function implementedMethods()
    {
        if(!isset($this->_actionController))
            return array();

        $class              = get_class($this->_actionController);
        $oReflector         = new ReflectionClass($class);
        $methods            = $oReflector->getMethods(ReflectionMethod::IS_PUBLIC);
        $implementedMethods = array();

        foreach($methods as $i => $method) /* @var $method ReflectionMethod */
        {
            if($method->getDeclaringClass()->getName() == $class)
                $implementedMethods[] = str_replace('Action', '', $method->getName());
        }

        return array_intersect($implementedMethods, array('get', 'put', 'post', 'delete', 'head', 'options', 'trace'));
    }
}

If you extend this controller for all your actions, you’ll get automated Allow headers every time someone calls a URL that does not implement their request type.

As I said in my talk, be sure to use an op-code cache so reflection doesn’t become your bottleneck!

Also I should mention, this code lives in my ApiController and not in an action helper or similar due to Zend Framework’s performance hit as a result of using magic methods.

Comments

comments

Bookmark the permalink.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.