Browse Source

Fix shutdown and afterDispatch events not being fired on exceptions.

When exceptions are fired the shutdown and afterDispatch events should
be properly fired. This allows plugins and application code to ensure it
is run. Of course this creates the potential for infinite loops if these
events also trigger an exception.

Refs #5208
Mark Story 11 years ago
parent
commit
a1cb67bc24

+ 0 - 1
src/Controller/ErrorController.php

@@ -43,7 +43,6 @@ class ErrorController extends Controller {
 		if (isset($this->Security)) {
 			$eventManager->detach($this->Security);
 		}
-		$this->cacheAction = false;
 		$this->viewPath = 'Error';
 	}
 

+ 21 - 3
src/Error/ExceptionRenderer.php

@@ -23,6 +23,7 @@ use Cake\Event\Event;
 use Cake\Network\Exception\HttpException;
 use Cake\Network\Request;
 use Cake\Network\Response;
+use Cake\Routing\DispatcherFactory;
 use Cake\Routing\Router;
 use Cake\Utility\Inflector;
 use Cake\View\Exception\MissingTemplateException;
@@ -165,6 +166,7 @@ class ExceptionRenderer {
  */
 	protected function _customMethod($method, $exception) {
 		$result = call_user_func([$this, $method], $exception);
+		$this->_shutdown();
 		if (is_string($result)) {
 			$this->controller->response->body($result);
 			$result = $this->controller->response;
@@ -267,9 +269,7 @@ class ExceptionRenderer {
 	protected function _outputMessage($template) {
 		try {
 			$this->controller->render($template);
-			$event = new Event('Controller.shutdown', $this->controller);
-			$this->controller->afterFilter($event);
-			return $this->controller->response;
+			return $this->_shutdown();
 		} catch (MissingTemplateException $e) {
 			$attributes = $e->getAttributes();
 			if (isset($attributes['file']) && strpos($attributes['file'], 'error500') !== false) {
@@ -307,4 +307,22 @@ class ExceptionRenderer {
 		return $this->controller->response;
 	}
 
+/**
+ * Run the shutdown events.
+ *
+ * Triggers the afterFilter and afterDispatch events.
+ *
+ * @return \Cake\Network\Response The response to serve.
+ */
+	protected function _shutdown() {
+		$this->controller->dispatchEvent('Controller.shutdown');
+		$dispatcher = DispatcherFactory::create();
+		$args = [
+			'request' => $this->controller->request,
+			'response' => $this->controller->response
+		];
+		$result = $dispatcher->dispatchEvent('Dispatcher.afterDispatch', $args);
+		return $result->data['response'];
+	}
+
 }

+ 45 - 0
tests/TestCase/Error/ExceptionRendererTest.php

@@ -27,6 +27,7 @@ use Cake\Datasource\Exception\MissingDatasourceException;
 use Cake\Error;
 use Cake\Error\ExceptionRenderer;
 use Cake\Event\Event;
+use Cake\Event\EventManager;
 use Cake\Network\Exception\InternalErrorException;
 use Cake\Network\Exception\MethodNotAllowedException;
 use Cake\Network\Exception\NotFoundException;
@@ -760,6 +761,50 @@ class ExceptionRendererTest extends TestCase {
 	}
 
 /**
+ * Test that rendering exceptions triggers shutdown events.
+ *
+ * @return void
+ */
+	public function testRenderShutdownEvents() {
+		$fired = [];
+		$listener = function ($event) use (&$fired) {
+			$fired[] = $event->name();
+		};
+		$events = EventManager::instance();
+		$events->attach($listener, 'Controller.shutdown');
+		$events->attach($listener, 'Dispatcher.afterDispatch');
+
+		$exception = new \Exception('Terrible');
+		$renderer = new ExceptionRenderer($exception);
+		$renderer->render();
+
+		$expected = ['Controller.shutdown', 'Dispatcher.afterDispatch'];
+		$this->assertEquals($expected, $fired);
+	}
+
+/**
+ * test that subclass methods fire shutdown events.
+ *
+ * @return void
+ */
+	public function testSubclassTriggerShutdownEvents() {
+		$fired = [];
+		$listener = function ($event) use (&$fired) {
+			$fired[] = $event->name();
+		};
+		$events = EventManager::instance();
+		$events->attach($listener, 'Controller.shutdown');
+		$events->attach($listener, 'Dispatcher.afterDispatch');
+
+		$exception = new MissingWidgetThingException('Widget not found');
+		$renderer = $this->_mockResponse(new MyCustomExceptionRenderer($exception));
+		$renderer->render();
+
+		$expected = ['Controller.shutdown', 'Dispatcher.afterDispatch'];
+		$this->assertEquals($expected, $fired);
+	}
+
+/**
  * Tests the output of rendering a PDOException
  *
  * @return void