From 5d67195bf7e82fc9911984be1c6bbac9c87c494c Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Sat, 24 Dec 2011 21:00:39 -0430 Subject: [PATCH] Migrating Controller events to use the CakeEventManager --- lib/Cake/Controller/ComponentCollection.php | 20 +++++- lib/Cake/Controller/Controller.php | 54 +++++++++++--- .../Test/Case/Controller/ControllerTest.php | 71 ++++++++++++++----- lib/Cake/Utility/ObjectCollection.php | 13 +++- 4 files changed, 131 insertions(+), 27 deletions(-) diff --git a/lib/Cake/Controller/ComponentCollection.php b/lib/Cake/Controller/ComponentCollection.php index 4f688482c6b..c1ff24fbb08 100644 --- a/lib/Cake/Controller/ComponentCollection.php +++ b/lib/Cake/Controller/ComponentCollection.php @@ -18,6 +18,7 @@ App::uses('ObjectCollection', 'Utility'); App::uses('Component', 'Controller'); +App::uses('CakeEventListener', 'Event'); /** * Components collection is used as a registry for loaded components and handles loading @@ -25,7 +26,7 @@ * * @package Cake.Controller */ -class ComponentCollection extends ObjectCollection { +class ComponentCollection extends ObjectCollection implements CakeEventListener { /** * The controller that this collection was initialized with. @@ -108,4 +109,21 @@ public function load($component, $settings = array()) { return $this->_loaded[$alias]; } +/** + * Returns the implemented events that will get routed to the trigger function + * in order to dispatch them separately on each component + * + * @return array + */ + public function implementedEvents() { + return array( + 'Controller.startup' => array( + array('callable' => 'trigger', 'priority' => 9), + array('callable' => 'trigger') + ), + 'Controller.beforeRender' => array('callable' => 'trigger'), + 'Controller.beforeRedirect' => array('callable' => 'trigger'), + 'Controller.shutdown' => array('callable' => 'trigger', 'priority' => 9), + ); + } } \ No newline at end of file diff --git a/lib/Cake/Controller/Controller.php b/lib/Cake/Controller/Controller.php index b32e2ba2491..64a8a14f2f1 100644 --- a/lib/Cake/Controller/Controller.php +++ b/lib/Cake/Controller/Controller.php @@ -24,6 +24,9 @@ App::uses('ClassRegistry', 'Utility'); App::uses('ComponentCollection', 'Controller'); App::uses('View', 'View'); +App::uses('CakeEvent', 'Event'); +App::uses('CakeEventListener', 'Event'); +App::uses('CakeEventManager', 'Event'); /** * Application controller class for organization of business logic. @@ -57,7 +60,7 @@ * @property SessionComponent $Session * @link http://book.cakephp.org/2.0/en/controllers.html */ -class Controller extends Object { +class Controller extends Object implements CakeEventListener { /** * The name of this controller. Controller names are plural, named after the model they manipulate. @@ -294,6 +297,14 @@ class Controller extends Object { */ protected $_mergeParent = 'AppController'; +/** + * Instance of the CakeEventManager this controller is using + * to dispatch inner events. + * + * @var CakeEventManager + */ + protected $_eventManager = null; + /** * Constructor. * @@ -570,6 +581,21 @@ protected function _mergeControllerVars() { } } +/** + * Returns a list of all events that will fire in the controller during it's lifecycle. + * You can override this function to add you own listener callbacks + * + * @return array + */ + public function implementedEvents() { + return array( + 'Controller.startup' => 'beforeFilter', + 'Controller.beforeRender' => 'beforeRender', + 'Controller.beforeRedirect' => array('callable' => 'beforeRedirect', 'passParams' => true), + 'Controller.shutdown' => 'afterFilter' + ); + } + /** * Loads Model classes based on the uses property * see Controller::loadModel(); for more info. @@ -590,6 +616,22 @@ public function constructClasses() { return true; } +/** + * Returns the CakeEventManager manager instance that is handling any callbacks. + * You can use this instance to register any new listeners or callbacks to the + * controller events, or create your own events and trigger them at will. + * + * @return CakeEventManager + */ + public function getEventManager() { + if (empty($this->_eventManager)) { + $this->_eventManager = new CakeEventManager(); + $this->_eventManager->attach($this); + $this->_eventManager->attach($this->Components); + } + return $this->_eventManager; + } + /** * Perform the startup process for this controller. * Fire the Components and Controller callbacks in the correct order. @@ -601,9 +643,7 @@ public function constructClasses() { * @return void */ public function startupProcess() { - $this->Components->trigger('initialize', array(&$this)); - $this->beforeFilter(); - $this->Components->trigger('startup', array(&$this)); + $this->getEventManager()->dispatch(new CakeEvent('Controller.startup', $this)); } /** @@ -616,8 +656,7 @@ public function startupProcess() { * @return void */ public function shutdownProcess() { - $this->Components->trigger('shutdown', array(&$this)); - $this->afterFilter(); + $this->getEventManager()->dispatch(new CakeEvent('Controller.shutdown', $this)); } /** @@ -862,8 +901,7 @@ public function validateErrors() { * @link http://book.cakephp.org/2.0/en/controllers.html#Controller::render */ public function render($view = null, $layout = null) { - $this->beforeRender(); - $this->Components->trigger('beforeRender', array(&$this)); + $this->getEventManager()->dispatch(new CakeEvent('Controller.beforeRender', $this)); $viewClass = $this->viewClass; if ($this->viewClass != 'View') { diff --git a/lib/Cake/Test/Case/Controller/ControllerTest.php b/lib/Cake/Test/Case/Controller/ControllerTest.php index a209e3213b2..0dfc31f2bcd 100644 --- a/lib/Cake/Test/Case/Controller/ControllerTest.php +++ b/lib/Cake/Test/Case/Controller/ControllerTest.php @@ -313,7 +313,7 @@ public function beforeRedirect() { * * @return void */ - public function initialize(&$controller) { + public function initialize($controller) { } /** @@ -321,7 +321,7 @@ public function initialize(&$controller) { * * @return void */ - public function startup(&$controller) { + public function startup($controller) { } /** @@ -329,7 +329,7 @@ public function startup(&$controller) { * * @return void */ - public function shutdown(&$controller) { + public function shutdown($controller) { } /** @@ -337,7 +337,7 @@ public function shutdown(&$controller) { * * @return void */ - public function beforeRender(&$controller) { + public function beforeRender($controller) { if ($this->viewclass) { $controller->viewClass = $this->viewclass; } @@ -1111,17 +1111,35 @@ public function testControllerHttpCodes() { * @return void */ public function testStartupProcess() { - $Controller = $this->getMock('Controller', array('beforeFilter', 'afterFilter')); + $Controller = $this->getMock('Controller', array('getEventManager')); + + $eventManager = $this->getMock('CakeEventManager'); + $eventManager->expects($this->once())->method('dispatch') + ->with( + $this->logicalAnd( + $this->isInstanceOf('CakeEvent'), + $this->attributeEqualTo('_name', 'Controller.startup'), + $this->attributeEqualTo('_subject', $Controller) + ) + ); + $Controller->expects($this->once())->method('getEventManager') + ->will($this->returnValue($eventManager)); + $Controller->startupProcess(); + } - $Controller->components = array('MockStartup'); - $Controller->Components = $this->getMock('ComponentCollection'); +/** + * Tests that the shutdown process calls the correct functions + * + * @return void + */ + public function testStartupProcessIndirect() { + $Controller = $this->getMock('Controller', array('beforeFilter')); - $Controller->expects($this->once())->method('beforeFilter'); - $Controller->Components->expects($this->at(0))->method('trigger') - ->with('initialize', array(&$Controller)); + $Controller->components = array('MockShutdown'); + $Controller->Components = $this->getMock('ComponentCollection', array('trigger')); - $Controller->Components->expects($this->at(1))->method('trigger') - ->with('startup', array(&$Controller)); + $Controller->expects($this->once())->method('beforeFilter'); + $Controller->Components->expects($this->exactly(2))->method('trigger')->with($this->isInstanceOf('CakeEvent')); $Controller->startupProcess(); } @@ -1132,14 +1150,35 @@ public function testStartupProcess() { * @return void */ public function testShutdownProcess() { - $Controller = $this->getMock('Controller', array('beforeFilter', 'afterFilter')); + $Controller = $this->getMock('Controller', array('getEventManager')); + + $eventManager = $this->getMock('CakeEventManager'); + $eventManager->expects($this->once())->method('dispatch') + ->with( + $this->logicalAnd( + $this->isInstanceOf('CakeEvent'), + $this->attributeEqualTo('_name', 'Controller.shutdown'), + $this->attributeEqualTo('_subject', $Controller) + ) + ); + $Controller->expects($this->once())->method('getEventManager') + ->will($this->returnValue($eventManager)); + $Controller->shutdownProcess(); + } + +/** + * Tests that the shutdown process calls the correct functions + * + * @return void + */ + public function testShutdownProcessIndirect() { + $Controller = $this->getMock('Controller', array('afterFilter')); $Controller->components = array('MockShutdown'); - $Controller->Components = $this->getMock('ComponentCollection'); + $Controller->Components = $this->getMock('ComponentCollection', array('trigger')); $Controller->expects($this->once())->method('afterFilter'); - $Controller->Components->expects($this->once())->method('trigger') - ->with('shutdown', array(&$Controller)); + $Controller->Components->expects($this->exactly(1))->method('trigger')->with($this->isInstanceOf('CakeEvent')); $Controller->shutdownProcess(); } diff --git a/lib/Cake/Utility/ObjectCollection.php b/lib/Cake/Utility/ObjectCollection.php index 333d241389c..a49a75fa069 100644 --- a/lib/Cake/Utility/ObjectCollection.php +++ b/lib/Cake/Utility/ObjectCollection.php @@ -82,8 +82,10 @@ abstract public function load($name, $options = array()); * Defaults to false. * * - * @param string $callback Method to fire on all the objects. Its assumed all the objects implement - * the method you are calling. + * @param string $callback|CakeEvent Method to fire on all the objects. Its assumed all the objects implement + * the method you are calling. If an instance of CakeEvent is provided, then then Event name will parsed to + * get the callback name. This is done by getting the last word after any dot in the event name + * (eg. `Model.afterSave` event will trigger the `afterSave` callback) * @param array $params Array of parameters for the triggered callback. * @param array $options Array of options. * @return mixed Either the last result or all results if collectReturn is on. @@ -93,6 +95,13 @@ public function trigger($callback, $params = array(), $options = array()) { if (empty($this->_enabled)) { return true; } + if ($callback instanceof CakeEvent) { + if (is_array($callback->data)) { + $params = $callback->data; + } + $params = array($callback->subject()); + $callback = array_pop(explode('.', $callback->name())); + } $options = array_merge( array( 'break' => false,