Zend_MVC, Controller Plugins and Annotations
While it is common practice in many enterprise grade environments to use annotations to not only document but also influence code, I am missing some of this goodness in Zend Framework MVC (1.x) applications.
Recently I had the idea to influence Controller Actions with annotations but discarded it with thoughts like “In PHP I will have to use reflection and some black magic to get this working which will have insane performance hits for my applications”.
… until I set everything up to see that it costs just 1-2ms in average per request without any form of caching.
As example this is code I use to set the used layout script via @layout annotation:
Before:
public function fooAction()
{
$this->_helpers->layout->setLayout('bar');
//...
}
After:
/**
* @layout bar
*/
public function fooAction()
{
//...
}
Abstract Class (reflection = php5.3) to get the action’s docblock
This needs more work to reflect the configurable module/controller directory and prefixDefaultModule Zend_MVC settings – this may not work for you out of the box.
/**
* @author Karsten Deubert <karsten@deubert.net>
*/
abstract class My_Controller_Plugin_Annotation_Abstract extends Zend_Controller_Plugin_Abstract
{
/**
* @return string
*/
protected function _getDocblockForControllerAction()
{
/** @var $request Zend_Controller_Request_Http */
$request = $this->getRequest();
$frontController = Zend_Controller_Front::getInstance();
/** @var $dispatcher Zend_Controller_Dispatcher_Standard */
$dispatcher = $frontController->getDispatcher();
$controllerName =
ucfirst($dispatcher->formatModuleName($request->getModuleName()))
.'_'
.$dispatcher->formatControllerName($request->getControllerName());
$controllerPath =
$frontController->getModuleDirectory($request->getModuleName())
.DIRECTORY_SEPARATOR
.$frontController->getModuleControllerDirectoryName()
.DIRECTORY_SEPARATOR
.$dispatcher->formatControllerName($request->getControllerName())
.'.php';
require_once $controllerPath;
$actionName = $dispatcher->formatActionName($request->getActionName());
$reflectionClass = new ReflectionClass($controllerName);
if (!$reflectionClass->hasMethod($actionName))
{
return '';
}
$reflectionMethod = $reflectionClass->getMethod($actionName);
$docBlock = $reflectionMethod->getDocComment();
return $docBlock;
}
}
Controller Plugin code:
/**
* @author Karsten Deubert <karsten@deubert.net>
*/
class My_Controller_Plugin_Annotation_Layout extends My_Controller_Plugin_Annotation_Abstract
{
/**
* @param Zend_Controller_Request_Abstract $request
* @return void
*/
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
/** @var $request Zend_Controller_Request_Http */
if (!$request instanceof Zend_Controller_Request_Http)
{
return;
}
$docBlock = $this->_getDocblockForControllerAction();
$pattern = '#@layout (\S+)#iu';
$matches = array();
$matchesCount = preg_match($pattern, $docBlock, $matches);
if (!$matchesCount)
{
return;
}
$layout = Zend_Layout::getMvcInstance();
if ($matches[1] === 'disabled')
{
$layout->disableLayout();
}
else
{
$layout->setLayout($matches[1]);
}
}
}
I have several Annotation Controller Plugins in place for very different purposes and… love them!

email me
follow me
