Skip to content

Commit

Permalink
First pass at binding components to the eventmanager
Browse files Browse the repository at this point in the history
Much like helpers, components should bind their events directly to the
event manager. This will make the events more efficient and less special
from user-land events. It also focuses the ComponentCollection on being
a factory which makes it simpler as well.
  • Loading branch information
markstory committed Jul 18, 2013
1 parent 0b3f817 commit 83281e5
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 93 deletions.
31 changes: 30 additions & 1 deletion lib/Cake/Controller/Component.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
namespace Cake\Controller;

use Cake\Core\Object;
use Cake\Event\EventListener;

/**
* Base class for an individual Component. Components provide reusable bits of
Expand All @@ -37,7 +38,7 @@
* @link http://book.cakephp.org/2.0/en/controllers/components.html
* @see Controller::$components
*/
class Component extends Object {
class Component extends Object implements EventListener {

/**
* Component collection class used to lazy load components.
Expand Down Expand Up @@ -161,4 +162,32 @@ public function shutdown(Controller $controller) {
public function beforeRedirect(Controller $controller, $url, $status = null, $exit = true) {
}

/**
* Get the Controller callbacks this Component is interested in.
*
* Uses Conventions to map controller events to standard component
* callback method names. By defining one of the callback methods a
* component is assumed to be interested in the related event.
*
* Override this method if you need to add non-conventional event listeners.
* Or if you want components to listen to non-standard events.
*
* @return array
*/
public function implementedEvents() {
$eventMap = [
'Controller.initialize' => 'initialize',
'Controller.startup' => 'startup',
'Controller.beforeRender' => 'beforeRender',
'Controller.beforeRedirect' => 'beforeRedirect',
'Controller.shutdown' => 'shutdown',
];
$events = [];
foreach ($eventMap as $event => $method) {
if (method_exists($this, $method)) {
$events[$event] = $method;
}
}
return $events;
}
}
89 changes: 55 additions & 34 deletions lib/Cake/Controller/ComponentCollection.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
<?php
/**
* Components collection is used as a registry for loaded components and handles loading
* and constructing component class objects.
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
Expand All @@ -12,7 +9,6 @@
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Controller
* @since CakePHP(tm) v 2.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
Expand All @@ -21,15 +17,20 @@
use Cake\Core\App;
use Cake\Error;
use Cake\Event\EventListener;
use Cake\Utility\ObjectCollection;

/**
* Components collection is used as a registry for loaded components and handles loading
* and constructing component class objects.
* Components collection is used as a registry for loaded components
*
* Handles loading, constructing and binding events for component class objects.
*/
class ComponentCollection {

/**
* Loaded objects
*
* @package Cake.Controller
* @var array
*/
class ComponentCollection extends ObjectCollection implements EventListener {
protected $_loaded = [];

/**
* The controller that this collection was initialized with.
Expand All @@ -39,21 +40,20 @@ class ComponentCollection extends ObjectCollection implements EventListener {
protected $_Controller = null;

/**
* Initializes all the Components for a controller.
* Attaches a reference of each component to the Controller.
* The event manager to bind components to.
*
* @param Controller $Controller Controller to initialize components for.
* @return void
* @var Cake\Event\EventManager
*/
public function init(Controller $Controller) {
if (empty($Controller->components)) {
return;
}
protected $_eventManager = null;

/**
* Constructor.
*
* @param Cake\Controller\Controller $Controller
*/
public function __construct(Controller $Controller) {
$this->_Controller = $Controller;
$components = static::normalizeObjectArray($Controller->components);
foreach ($components as $name => $properties) {
$Controller->{$name} = $this->load($properties['class'], $properties['settings']);
}
$this->_eventManager = $Controller->getEventManager();
}

/**
Expand Down Expand Up @@ -104,28 +104,49 @@ public function load($component, $settings = array()) {
'plugin' => substr($plugin, 0, -1)
));
}
$this->_loaded[$alias] = new $componentClass($this, $settings);
$component = new $componentClass($this, $settings);
$enable = isset($settings['enabled']) ? $settings['enabled'] : true;
if ($enable) {
$this->enable($alias);
$this->_eventManager->attach($component);
}
$this->_loaded[$alias] = $component;
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
* Get the loaded components list, or get the component instance at a given name.
*
* @param null|string $name The component name to get or null.
* @return array|Helper Either a list of components names, or a loaded component.
*/
public function loaded($name = null) {
if (!empty($name)) {
return isset($this->_loaded[$name]);
}
return array_keys($this->_loaded);
}

/**
* Provide public read access to the loaded objects
*
* @param string $name Name of property to read
* @return mixed
*/
public function __get($name) {
if (isset($this->_loaded[$name])) {
return $this->_loaded[$name];
}
return null;
}

/**
* Provide isset access to _loaded
*
* @return array
* @param string $name Name of object being checked.
* @return boolean
*/
public function implementedEvents() {
return array(
'Controller.initialize' => array('callable' => 'trigger'),
'Controller.startup' => array('callable' => 'trigger'),
'Controller.beforeRender' => array('callable' => 'trigger'),
'Controller.beforeRedirect' => array('callable' => 'trigger'),
'Controller.shutdown' => array('callable' => 'trigger'),
);
public function __isset($name) {
return isset($this->_loaded[$name]);
}

}
53 changes: 43 additions & 10 deletions lib/Cake/Controller/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Cake\Core\App;
use Cake\Core\Configure;
use Cake\Core\Object;
use Cake\Core\ObjectCollection;
use Cake\Core\Plugin;
use Cake\Error;
use Cake\Event\Event;
Expand Down Expand Up @@ -181,7 +182,7 @@ class Controller extends Object implements EventListener {
public $autoLayout = true;

/**
* Instance of ComponentCollection used to handle callbacks.
* Instance of ComponentCollection used to create Components
*
* @var ComponentCollection
*/
Expand Down Expand Up @@ -329,7 +330,6 @@ public function __construct($request = null, $response = null) {

$this->modelClass = Inflector::singularize($this->name);
$this->modelKey = Inflector::underscore($this->modelClass);
$this->Components = new ComponentCollection();

$childMethods = get_class_methods($this);
$parentMethods = get_class_methods('Cake\Controller\Controller');
Expand All @@ -342,7 +342,7 @@ public function __construct($request = null, $response = null) {
if ($response instanceof Response) {
$this->response = $response;
}
parent::__construct();
$this->Components = new ComponentCollection($this);
}

/**
Expand Down Expand Up @@ -571,9 +571,13 @@ public function implementedEvents() {
}

/**
* Loads Model classes based on the uses property
* see Controller::loadModel(); for more info.
* Loads Components and prepares them for initialization.
* Loads Model and Component classes.
*
* Using the $uses and $components properties, classes are loaded
* and components have their callbacks attached to the EventManager.
* It is also at this time that Controller callbacks are bound.
*
* See Controller::loadModel(); for more information on how models are loaded.
*
* @return mixed true if models found and instance created.
* @see Controller::loadModel()
Expand All @@ -586,12 +590,31 @@ public function constructClasses() {
$this->uses = (array)$this->uses;
list(, $this->modelClass) = pluginSplit(reset($this->uses));
}
$this->Components->init($this);

$this->_loadComponents();
$this->getEventManager()->attach($this);
return true;
}

/**
* Returns the Cake\Event\EventManager manager instance that is handling any callbacks.
* Loads the defined components using the Component factory.
*
* @return void
*/
protected function _loadComponents() {
if (empty($this->components)) {
return;
}
$components = ObjectCollection::normalizeObjectArray($this->components);
foreach ($components as $properties) {
list(, $class) = pluginSplit($properties['class']);
$this->{$class} = $this->Components->load($properties['class'], $properties['settings']);
}
}

/**
* Returns the Cake\Event\EventManager manager instance for this controller.
*
* 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.
*
Expand All @@ -600,12 +623,22 @@ public function constructClasses() {
public function getEventManager() {
if (empty($this->_eventManager)) {
$this->_eventManager = new EventManager();
$this->_eventManager->attach($this->Components);
$this->_eventManager->attach($this);
}
return $this->_eventManager;
}

/**
* Overwrite the existing EventManager
*
* Useful for testing
*
* @param Cake\Event\EventManager $eventManager
* @return void
*/
public function setEventManager($eventManager) {
$this->_eventManager = $eventManager;
}

/**
* Perform the startup process for this controller.
* Fire the Components and Controller callbacks in the correct order.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
<?php
/**
* Short description for file.
*
* PHP 5
*
* CakePHP(tm) Tests <http://book.cakephp.org/2.0/en/development/testing.html>
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
Expand All @@ -13,19 +9,17 @@
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
* @package Cake.Test.TestApp.Plugin.TestPlugin.Controller.Component
* @since CakePHP(tm) v 1.2.0.4206
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/

/**
* Class OtherComponent
*
* @package Cake.Test.TestApp.Plugin.TestPlugin.Controller.Component
*/
namespace TestPlugin\Controller\Component;

use Cake\Core\Object;
use Cake\Controller\Component;

class OtherComponent extends Object {
class OtherComponent extends Component {
}
Loading

0 comments on commit 83281e5

Please sign in to comment.