Do Action Helpers with hooks to auto-run, throw exceptions or not?
UPDATED
Wanting some code to run with each controller, and being told to use Action Helpers or a Plugin rather than extending from a Base Controller, I decided on an Action Helper rather than Plugin, per excellent slides by @Bittarman (Ryan Mauger);
Zend Framework, getting to grips: http://www.slideshare.net/rmauger/zend-framework-getting-to-grips See slide 22: Thrown Exceptions in (action helpers) Pre/Post Dispatch will stop further开发者_运维问答 execution...
While it DOES stop further execution, the exception wasn't caught. I've been trying to debug this for hours but not getting anywhere.
If you run the following code, are you seeing exceptions caught or is it escaping the Error Controller?
I'm trying to figure out whether Zend Framework is NOT behaving as expected, or if I totally messed something up (more likely).
I tried to break this down into the simplest case to replicate, let me know what you see:
/* add to existing Bootstrap located here: APPLICATION_PATH/Bootstrap.php */
protected function _initActionHelpers()
{
Zend_Controller_Action_HelperBroker::addPath(APPLICATION_PATH .'/controllers/helpers');
//hooks cause action helper to autorun: http://akrabat.com/zend-framework/hooks-in-action-helpers/
$hooks = Zend_Controller_Action_HelperBroker::getStaticHelper('Test');
Zend_Controller_Action_HelperBroker::addHelper($hooks);
}
/* in: APPLICATION_PATH/controllers/helpers/Test.php */
<?php
class Zend_Controller_Action_Helper_Test extends Zend_Controller_Action_Helper_Abstract
{
public function preDispatch()
{
// you can skip next line if you don't have xdebug
//xdebug_disable();
throw new Exception('test', 404);
parent::preDispatch();
}
}
Update: OK, I've been running this through xDebug + Eclipse... (it was either that or have fun poking my eyes out, not sure if I chose the more pleasurable experience)....and I found out something bizarre.
The preDispatch is running twice! And on the second call, it goes to the Zend_Controller_Plugin/ErrorHandler.php where it runs this code:
if ($this->_isInsideErrorHandlerLoop) {
$exceptions = $response->getException();
if (count($exceptions) > $this->_exceptionCountAtFirstEncounter) {
// Exception thrown by error handler; tell the front controller to throw it
$frontController->throwExceptions(true);
throw array_pop($exceptions);
}
}
By setting throw Exceptions to true, it no longer gets caught. And I suspect this is to save a loop from occurring ($this->_isInsideErrorHandlerLoop is a subtle clue too ;)
Why is it in a loop?
OK, @Bittarman gave me the answer in #ZFTalk on IRC. Here it is (and apologies that I will mark my own answer as correct..but I don't want to bug him even more to write it up here).
if( ($this->getRequest()->getActionName() == 'error') && ($this->getRequest()->getControllerName() == 'error')) {
return;
}
The reason for this is that the error handler has to disable itself (by setting throw exceptions to true) when its in the error handler loop. So you have to make sure your exception condition does not occur in the error controller.
The ErrorHandler plugin is designed to catch exceptions thrown by the mvc-component, not by plugins. It hooks into the postDispatch and checks for exceptions of the dispatch - and nothing else. It won't catch exceptions prior to this like in the preDispatch.
If you have a look at the source of the ErrorHandler Plugin you can see that it checks for Exceptions in the Request object. This only works if the frontController option 'throwExceptions' is set to false which makes him to catch exceptions and store them for later use (like the plugin handler).
In your case the exception isn't caught by the frontcontroller thus thrown to the very top of the application. You sure can edit the frontController to catch exceptions prior to the dispatch - but this isn't implemented intentional: everything prior to the dispatch can be seen as "booting" the application: any exception there is critical. Like in real booting, any exception there stops the application to run. So you really should avoid any exceptions there.
Update: Ok this was for the plugin part and forgot you wanted to know for the ActionHelper part. Those aren't executed by the FrontController but by the Zend_Controller_Action. As there they're part of the dispatching and caught by the FrontController and handled by the ErrorHandler plugin.
But what does the ErrorHandler do? It adds another request to the FrontController with your errorController. So another dispatching loop is done and again, your actionhelper does throws an exception. As the errorController request isn't different to any other request, the ErrorHandler plugin would catch it again and chain another request of the errorController. Which again.. i guess you can see where this would lead to - a loop. To prevent this there is a property to see if there was already an exception somewhere. This is to catch any exception thrown by the error handler. If the ErrorHandler plugin notices such an exception it overwrites the throwException property to true and throws the exception.
So the unhandled exception you can see is the exception which rises from the errorController request.
I can see the point that ActionHelpers can throw an exception - but you should be really careful with that for exactly this reason ;)
精彩评论