Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3.0 middleware events #3495

Merged
merged 24 commits into from May 16, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0adf133
Hacky first/incomplete implementation of extending existing filters.
markstory May 7, 2014
36066a2
Add docs for features that are coming soon.
markstory May 8, 2014
7d9217b
Add very basic test for DispatcherFactory.
markstory May 8, 2014
5ad0d6f
Start test cases for middleware.
markstory May 8, 2014
7a808d0
Implement matches() and add in progress test cases.
markstory May 9, 2014
3c86c3f
Implement remaining tests for DispatcherFilter.
markstory May 9, 2014
0c95f47
Mark broken tests as incomplete and remove dead code.
markstory May 9, 2014
c0666fb
Make priority a configuration value.
markstory May 9, 2014
5e7677b
Add tests for RoutingFilter.
markstory May 9, 2014
275a4b9
Remove old filter tests, and update asset dispatcher tests.
markstory May 10, 2014
e6092c1
Move AssetDispatcher tests to correct test file.
markstory May 10, 2014
a33ee0c
Move CacheDispatcher tests into their own file.
markstory May 10, 2014
65f4a12
Clean up remaining dispatcher tests.
markstory May 10, 2014
25c5cc3
Add string filter support.
markstory May 10, 2014
d367c8a
Remove additionalParams and update RequestActionTrait tests.
markstory May 10, 2014
2177707
Fix failing tests in ControllerTestCase.
markstory May 12, 2014
7db068c
Add a convention for dispatcher filter names.
markstory May 14, 2014
9b7c82d
Add missing doc blocks and rename methods to be more consistent.
markstory May 15, 2014
a9d3a61
Allow an array of options to be passed into a dispatcher filter.
markstory May 15, 2014
c8baef1
Expand on docs around event priorities.
markstory May 16, 2014
4803c4a
Move controller resolution into a separate dispatcher filter.
markstory May 16, 2014
d2a8a9e
Remove constructor that does nothing other than bad things.
markstory May 16, 2014
6fc2fd1
Make RoutingFilter apply after Asset and Cache filters.
markstory May 16, 2014
7301b32
CS fix
lorenzo May 16, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Event/EventManager.php
Expand Up @@ -89,8 +89,8 @@ public static function instance($manager = null) {
* is an instance of Cake\Event\EventListener this argument will be ignored
*
* @param array $options used to set the `priority` flag to the listener. In the future more options may be added.
* Priorities are handled like queues, and multiple attachments added to the same priority queue will be treated in
* the order of insertion.
* Priorities are treated as queues. Lower values are called before higher ones, and multiple attachments
* added to the same priority queue will be treated in the order of insertion.
*
* @return void
* @throws \InvalidArgumentException When event key is missing or callable is not an
Expand Down
145 changes: 23 additions & 122 deletions src/Routing/Dispatcher.php
@@ -1,10 +1,5 @@
<?php
/**
* Dispatcher takes the URL information, parses it for parameters and
* tells the involved controllers what to do.
*
* This is the heart of CakePHP's operation.
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
Expand Down Expand Up @@ -38,7 +33,7 @@
* the controller.
*
*/
class Dispatcher implements EventListener {
class Dispatcher {

/**
* Event manager, used to handle dispatcher filters
Expand All @@ -48,15 +43,11 @@ class Dispatcher implements EventListener {
protected $_eventManager;

/**
* Constructor.
* Connected filter objects
*
* @param string $base The base directory for the application. Writes `App.base` to Configure.
* @var array
*/
public function __construct($base = false) {
if ($base !== false) {
Configure::write('App.base', $base);
}
}
protected $_filters = [];

/**
* Returns the Cake\Event\EventManager instance or creates one if none was
Expand All @@ -67,61 +58,10 @@ public function __construct($base = false) {
public function getEventManager() {
if (!$this->_eventManager) {
$this->_eventManager = new EventManager();
$this->_eventManager->attach($this);
$this->_attachFilters($this->_eventManager);
}
return $this->_eventManager;
}

/**
* Returns the list of events this object listens to.
*
* @return array
*/
public function implementedEvents() {
return array('Dispatcher.beforeDispatch' => 'parseParams');
}

/**
* Attaches all event listeners for this dispatcher instance. Loads the
* dispatcher filters from the configured locations.
*
* @param \Cake\Event\EventManager $manager
* @return void
* @throws \Cake\Routing\Error\MissingDispatcherFilterException
*/
protected function _attachFilters($manager) {
$filters = Configure::read('Dispatcher.filters');
if (empty($filters)) {
return;
}

foreach ($filters as $index => $filter) {
$settings = array();
if (is_array($filter) && !is_int($index)) {
$settings = $filter;
$filter = $index;
}
if (is_string($filter)) {
$filter = array('callable' => $filter);
}
if (is_string($filter['callable'])) {
$callable = App::className($filter['callable'], 'Routing/Filter');
if (!$callable) {
throw new Error\MissingDispatcherFilterException($filter['callable']);
}
$manager->attach(new $callable($settings));
} else {
$on = strtolower($filter['on']);
$options = array();
if (isset($filter['priority'])) {
$options = array('priority' => $filter['priority']);
}
$manager->attach($filter['callable'], 'Dispatcher.' . $on . 'Dispatch', $options);
}
}
}

/**
* Dispatches and invokes given Request, handing over control to the involved controller. If the controller is set
* to autoRender, via Controller::$autoRender, then Dispatcher will render the view.
Expand All @@ -136,12 +76,11 @@ protected function _attachFilters($manager) {
*
* @param \Cake\Network\Request $request Request object to dispatch.
* @param \Cake\Network\Response $response Response object to put the results of the dispatch into.
* @param array $additionalParams Settings array ("bare", "return") which is melded with the GET and POST params
* @return string|void if `$request['return']` is set then it returns response body, null otherwise
* @throws \Cake\Controller\Error\MissingControllerException When the controller is missing.
*/
public function dispatch(Request $request, Response $response, array $additionalParams = array()) {
$beforeEvent = new Event('Dispatcher.beforeDispatch', $this, compact('request', 'response', 'additionalParams'));
public function dispatch(Request $request, Response $response) {
$beforeEvent = new Event('Dispatcher.beforeDispatch', $this, compact('request', 'response'));
$this->getEventManager()->dispatch($beforeEvent);

$request = $beforeEvent->data['request'];
Expand All @@ -153,7 +92,10 @@ public function dispatch(Request $request, Response $response, array $additional
return;
}

$controller = $this->_getController($request, $response);
$controller = false;
if (isset($beforeEvent->data['controller'])) {
$controller = $beforeEvent->data['controller'];
}

if (!($controller instanceof Controller)) {
throw new MissingControllerException(array(
Expand Down Expand Up @@ -212,68 +154,27 @@ protected function _invoke(Controller $controller) {
}

/**
* Applies Routing and additionalParameters to the request to be dispatched.
* If Routes have not been loaded they will be loaded, and app/Config/routes.php will be run.
* Add a filter to this dispatcher.
*
* @param \Cake\Event\Event $event containing the request, response and additional params
* @return void
*/
public function parseParams(Event $event) {
$request = $event->data['request'];
Router::setRequestInfo($request);

if (empty($request->params['controller'])) {
$params = Router::parse($request->url);
$request->addParams($params);
}

if (!empty($event->data['additionalParams'])) {
$request->addParams($event->data['additionalParams']);
}
}

/**
* Get controller to use, either plugin controller or application controller
* The added filter will be attached to the event manager used
* by this dispatcher.
*
* @param \Cake\Network\Request $request Request object
* @param \Cake\Network\Response $response Response for the controller.
* @return mixed name of controller if not loaded, or object if loaded
* @param \Cake\Event\EventListener $filter The filter to connect. Can be
* any EventListener. Typically an instance of \Cake\Routing\DispatcherFilter.
* @return void
*/
protected function _getController($request, $response) {
$ctrlClass = $this->_loadController($request);
if (!$ctrlClass) {
return false;
}
$reflection = new \ReflectionClass($ctrlClass);
if ($reflection->isAbstract() || $reflection->isInterface()) {
return false;
}
return $reflection->newInstance($request, $response);
public function addFilter(EventListener $filter) {
$this->_filters[] = $filter;
$this->getEventManager()->attach($filter);
}

/**
* Load controller and return controller class name
* Get the list of connected filters.
*
* @param \Cake\Network\Request $request
* @return string|bool Name of controller class name
* @return array
*/
protected function _loadController($request) {
$pluginName = $pluginPath = $controller = null;
$namespace = 'Controller';
if (!empty($request->params['plugin'])) {
$pluginName = Inflector::camelize($request->params['plugin']);
$pluginPath = $pluginName . '.';
}
if (!empty($request->params['controller'])) {
$controller = Inflector::camelize($request->params['controller']);
}
if (!empty($request->params['prefix'])) {
$namespace .= '/' . Inflector::camelize($request->params['prefix']);
}
if ($pluginPath . $controller) {
return App::className($pluginPath . $controller, $namespace, 'Controller');
}
return false;
public function filters() {
return $this->_filters;
}

}
103 changes: 103 additions & 0 deletions src/Routing/DispatcherFactory.php
@@ -0,0 +1,103 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Routing;

use Cake\Core\App;
use Cake\Routing\Dispatcher;
use Cake\Routing\Error\MissingDispatcherFilterException;

/**
* A factory for creating dispatchers with all the desired middleware
* connected.
*/
class DispatcherFactory {

/**
* Stack of middleware to apply to dispatchers.
*
* @var array
*/
protected static $_stack = [];

/**
* Add a new middleware object to the stack of middleware
* that will be executed.
*
* Instances of filters will be re-used across all sub-requests
* in a request.
*
* @param string|\Cake\Routing\DispatcherFilter $filter Either the classname of the filter
* or an instance to use.
* @param array $options Constructor arguments/options for the filter if you are using a string name.
* If you are passing an instance, this argument will be ignored.
* @return \Cake\Routing\DispatcherFilter
*/
public static function add($filter, array $options = []) {
if (is_string($filter)) {
$filter = static::_createFilter($filter, $options);
}
static::$_stack[] = $filter;
return $filter;
}

/**
* Create an instance of a filter.
*
* @param string $name The name of the filter to build.
* @param array $options Constructor arguments/options for the filter.
* @return \Cake\Routing\DispatcherFilter
* @throws \Cake\Routing\Error\MissingDispatcherFilterException When filters cannot be found.
*/
protected static function _createFilter($name, $options) {
$className = App::className($name, 'Routing/Filter', 'Filter');
if (!$className) {
$msg = sprintf('Cannot locate dispatcher filter named "%s".', $name);
throw new MissingDispatcherFilterException($msg);
}
return new $className($options);
}

/**
* Create a dispatcher that has all the configured middleware applied.
*
* @return \Cake\Routing\Dispatcher
*/
public static function create() {
$dispatcher = new Dispatcher();
foreach (static::$_stack as $middleware) {
$dispatcher->addFilter($middleware);
}
return $dispatcher;
}

/**
* Get the connected dispatcher filters.
*
* @return array
*/
public static function filters() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's odd it is called filters here but middleware in the Dispatcher

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed my mind halfway through, I will make things use filter consistently.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed now.

return static::$_stack;
}

/**
* Clear the middleware stack.
*
* @return void
*/
public static function clear() {
static::$_stack = [];
}

}