Browse Source

Integrate the new ActionDispatcher into Dispatcher

Reduce code duplication by integrating the new dispatcher with the old
one. This removes some protected methods, but that is within our
backwards compability guidelines.

I've also added a new Dispatcher.invokeController method to allow
IntegrationTests and other use cases to more easily access the
controller being used in a request. This will be useful long term when
we mainline the PSR7 dispatching.
Mark Story 10 years ago
parent
commit
268de40f2f

+ 14 - 2
src/Http/ActionDispatcher.php

@@ -78,14 +78,24 @@ class ActionDispatcher
      */
      */
     public function dispatch(Request $request, Response $response)
     public function dispatch(Request $request, Response $response)
     {
     {
-        Router::pushRequest($request);
+        if (Router::getRequest(true) !== $request) {
+            Router::pushRequest($request);
+        }
         $beforeEvent = $this->dispatchEvent('Dispatcher.beforeDispatch', compact('request', 'response'));
         $beforeEvent = $this->dispatchEvent('Dispatcher.beforeDispatch', compact('request', 'response'));
 
 
         $request = $beforeEvent->data['request'];
         $request = $beforeEvent->data['request'];
         if ($beforeEvent->result instanceof Response) {
         if ($beforeEvent->result instanceof Response) {
             return $beforeEvent->result;
             return $beforeEvent->result;
         }
         }
-        $controller = $this->factory->create($request, $response);
+
+        // Use the controller built by an beforeDispatch
+        // event handler if there is one.
+        if (isset($beforeEvent->data['controller'])) {
+            $controller = $beforeEvent->data['controller'];
+        } else {
+            $controller = $this->factory->create($request, $response);
+        }
+
         $response = $this->_invoke($controller);
         $response = $this->_invoke($controller);
         if (isset($request->params['return'])) {
         if (isset($request->params['return'])) {
             return $response;
             return $response;
@@ -104,6 +114,8 @@ class ActionDispatcher
      */
      */
     protected function _invoke(Controller $controller)
     protected function _invoke(Controller $controller)
     {
     {
+        $this->dispatchEvent('Dispatcher.invokeController', ['controller' => $controller]);
+
         $result = $controller->startupProcess();
         $result = $controller->startupProcess();
         if ($result instanceof Response) {
         if ($result instanceof Response) {
             return $result;
             return $result;

+ 6 - 60
src/Routing/Dispatcher.php

@@ -17,6 +17,7 @@ namespace Cake\Routing;
 use Cake\Controller\Controller;
 use Cake\Controller\Controller;
 use Cake\Event\EventDispatcherTrait;
 use Cake\Event\EventDispatcherTrait;
 use Cake\Event\EventListenerInterface;
 use Cake\Event\EventListenerInterface;
+use Cake\Http\ActionDispatcher;
 use Cake\Network\Request;
 use Cake\Network\Request;
 use Cake\Network\Response;
 use Cake\Network\Response;
 use LogicException;
 use LogicException;
@@ -58,69 +59,15 @@ class Dispatcher
      */
      */
     public function dispatch(Request $request, Response $response)
     public function dispatch(Request $request, Response $response)
     {
     {
-        $beforeEvent = $this->dispatchEvent('Dispatcher.beforeDispatch', compact('request', 'response'));
-
-        $request = $beforeEvent->data['request'];
-        if ($beforeEvent->result instanceof Response) {
-            if (isset($request->params['return'])) {
-                return $beforeEvent->result->body();
-            }
-            $beforeEvent->result->send();
-            return null;
-        }
-
-        if (!isset($beforeEvent->data['controller'])) {
-            throw new LogicException(
-                'The Dispatcher.beforeDispatch event did not create a controller. ' .
-                'Ensure you have added the ControllerFactoryFilter.'
-            );
+        $actionDispatcher = new ActionDispatcher(null, $this->eventManager());
+        foreach ($this->_filters as $filter) {
+            $actionDispatcher->addFilter($filter);
         }
         }
-        $controller = $beforeEvent->data['controller'];
-
-        $response = $this->_invoke($controller);
+        $response = $actionDispatcher->dispatch($request, $response);
         if (isset($request->params['return'])) {
         if (isset($request->params['return'])) {
             return $response->body();
             return $response->body();
         }
         }
-
-        $afterEvent = $this->dispatchEvent('Dispatcher.afterDispatch', compact('request', 'response'));
-        $afterEvent->data['response']->send();
-    }
-
-    /**
-     * Initializes the components and models a controller will be using.
-     * Triggers the controller action and invokes the rendering if Controller::$autoRender
-     * is true. If a response object is returned by controller action that is returned
-     * else controller's $response property is returned.
-     *
-     * @param \Cake\Controller\Controller $controller Controller to invoke
-     * @return \Cake\Network\Response The resulting response object
-     * @throws \LogicException If data returned by controller action is not an
-     *   instance of Response
-     */
-    protected function _invoke(Controller $controller)
-    {
-        $result = $controller->startupProcess();
-        if ($result instanceof Response) {
-            return $result;
-        }
-
-        $response = $controller->invokeAction();
-        if ($response !== null && !($response instanceof Response)) {
-            throw new LogicException('Controller action can only return an instance of Response');
-        }
-
-        if (!$response && $controller->autoRender) {
-            $response = $controller->render();
-        } elseif (!$response) {
-            $response = $controller->response;
-        }
-
-        $result = $controller->shutdownProcess();
-        if ($result instanceof Response) {
-            return $result;
-        }
-
-        return $response;
+        return $response->send();
     }
     }
 
 
     /**
     /**
@@ -136,7 +83,6 @@ class Dispatcher
     public function addFilter(EventListenerInterface $filter)
     public function addFilter(EventListenerInterface $filter)
     {
     {
         $this->_filters[] = $filter;
         $this->_filters[] = $filter;
-        $this->eventManager()->on($filter);
     }
     }
 
 
     /**
     /**

+ 3 - 1
src/Routing/Filter/RoutingFilter.php

@@ -48,7 +48,9 @@ class RoutingFilter extends DispatcherFilter
     public function beforeDispatch(Event $event)
     public function beforeDispatch(Event $event)
     {
     {
         $request = $event->data['request'];
         $request = $event->data['request'];
-        Router::setRequestInfo($request);
+        if (Router::getRequest(true) !== $request) {
+            Router::setRequestInfo($request);
+        }
 
 
         try {
         try {
             if (empty($request->params['controller'])) {
             if (empty($request->params['controller'])) {

+ 5 - 8
src/TestSuite/IntegrationTestCase.php

@@ -125,7 +125,6 @@ abstract class IntegrationTestCase extends TestCase
 
 
     /**
     /**
      *
      *
-     *
      * @var null|string
      * @var null|string
      */
      */
     protected $_cookieEncriptionKey = null;
     protected $_cookieEncriptionKey = null;
@@ -357,7 +356,7 @@ abstract class IntegrationTestCase extends TestCase
         $response = new Response();
         $response = new Response();
         $dispatcher = DispatcherFactory::create();
         $dispatcher = DispatcherFactory::create();
         $dispatcher->eventManager()->on(
         $dispatcher->eventManager()->on(
-            'Dispatcher.beforeDispatch',
+            'Dispatcher.invokeController',
             ['priority' => 999],
             ['priority' => 999],
             [$this, 'controllerSpy']
             [$this, 'controllerSpy']
         );
         );
@@ -379,15 +378,13 @@ abstract class IntegrationTestCase extends TestCase
      * Adds additional event spies to the controller/view event manager.
      * Adds additional event spies to the controller/view event manager.
      *
      *
      * @param \Cake\Event\Event $event A dispatcher event.
      * @param \Cake\Event\Event $event A dispatcher event.
+     * @param \Cake\Controller\Controller $controller Controller instance.
      * @return void
      * @return void
      */
      */
-    public function controllerSpy($event)
+    public function controllerSpy($event, $controller)
     {
     {
-        if (empty($event->data['controller'])) {
-            return;
-        }
-        $this->_controller = $event->data['controller'];
-        $events = $this->_controller->eventManager();
+        $this->_controller = $controller;
+        $events = $controller->eventManager();
         $events->on('View.beforeRender', function ($event, $viewFile) {
         $events->on('View.beforeRender', function ($event, $viewFile) {
             if (!$this->_viewName) {
             if (!$this->_viewName) {
                 $this->_viewName = $viewFile;
                 $this->_viewName = $viewFile;

+ 5 - 235
tests/TestCase/Routing/DispatcherTest.php

@@ -24,177 +24,6 @@ use Cake\Routing\Filter\ControllerFactoryFilter;
 use Cake\TestSuite\TestCase;
 use Cake\TestSuite\TestCase;
 
 
 /**
 /**
- * A testing stub that doesn't send headers.
- */
-class DispatcherMockResponse extends Response
-{
-
-    protected function _sendHeader($name, $value = null)
-    {
-        return $name . ' ' . $value;
-    }
-}
-
-/**
- * TestDispatcher class
- */
-class TestDispatcher extends Dispatcher
-{
-
-    /**
-     * Controller instance, made publicly available for testing
-     *
-     * @var Controller
-     */
-    public $controller;
-
-    /**
-     * invoke method
-     *
-     * @param \Cake\Controller\Controller $controller
-     * @return \Cake\Network\Response $response
-     */
-    protected function _invoke(Controller $controller)
-    {
-        $this->controller = $controller;
-        return parent::_invoke($controller);
-    }
-}
-
-/**
- * MyPluginAppController class
- *
- */
-class MyPluginAppController extends Controller
-{
-}
-
-/**
- * MyPluginController class
- *
- */
-class MyPluginController extends MyPluginAppController
-{
-
-    /**
-     * name property
-     *
-     * @var string
-     */
-    public $name = 'MyPlugin';
-
-    /**
-     * index method
-     *
-     * @return void
-     */
-    public function index()
-    {
-        return true;
-    }
-
-    /**
-     * add method
-     *
-     * @return void
-     */
-    public function add()
-    {
-        return true;
-    }
-
-    /**
-     * admin_add method
-     *
-     * @param mixed $id
-     * @return void
-     */
-    public function admin_add($id = null)
-    {
-        return $id;
-    }
-}
-
-/**
- * OtherPagesController class
- *
- */
-class OtherPagesController extends MyPluginAppController
-{
-
-    /**
-     * name property
-     *
-     * @var string
-     */
-    public $name = 'OtherPages';
-
-    /**
-     * display method
-     *
-     * @param string $page
-     * @return void
-     */
-    public function display($page = null)
-    {
-        return $page;
-    }
-
-    /**
-     * index method
-     *
-     * @return void
-     */
-    public function index()
-    {
-        return true;
-    }
-}
-
-/**
- * ArticlesTestAppController class
- *
- */
-class ArticlesTestAppController extends Controller
-{
-}
-
-/**
- * ArticlesTestController class
- *
- */
-class ArticlesTestController extends ArticlesTestAppController
-{
-
-    /**
-     * name property
-     *
-     * @var string
-     */
-    public $name = 'ArticlesTest';
-
-    /**
-     * admin_index method
-     *
-     * @return void
-     */
-    public function admin_index()
-    {
-        return true;
-    }
-
-    /**
-     * fake index method.
-     *
-     * @return void
-     */
-    public function index()
-    {
-        return true;
-    }
-}
-
-/**
  * DispatcherTest class
  * DispatcherTest class
  *
  *
  */
  */
@@ -217,7 +46,7 @@ class DispatcherTest extends TestCase
         Configure::write('App.webroot', 'webroot');
         Configure::write('App.webroot', 'webroot');
         Configure::write('App.namespace', 'TestApp');
         Configure::write('App.namespace', 'TestApp');
 
 
-        $this->dispatcher = new TestDispatcher();
+        $this->dispatcher = new Dispatcher();
         $this->dispatcher->addFilter(new ControllerFactoryFilter());
         $this->dispatcher->addFilter(new ControllerFactoryFilter());
     }
     }
 
 
@@ -330,14 +159,14 @@ class DispatcherTest extends TestCase
                 'controller' => 'Pages',
                 'controller' => 'Pages',
                 'action' => 'display',
                 'action' => 'display',
                 'pass' => ['extract'],
                 'pass' => ['extract'],
-                'return' => 1
             ]
             ]
         ]);
         ]);
         $response = $this->getMock('Cake\Network\Response');
         $response = $this->getMock('Cake\Network\Response');
+        $response->expects($this->once())
+            ->method('send');
 
 
-        $this->dispatcher->dispatch($url, $response);
-        $expected = 'Pages';
-        $this->assertEquals($expected, $this->dispatcher->controller->name);
+        $result = $this->dispatcher->dispatch($url, $response);
+        $this->assertNull($result);
     }
     }
 
 
     /**
     /**
@@ -365,65 +194,6 @@ class DispatcherTest extends TestCase
     }
     }
 
 
     /**
     /**
-     * testPrefixDispatch method
-     *
-     * @return void
-     */
-    public function testPrefixDispatch()
-    {
-        $request = new Request([
-            'url' => 'admin/posts/index',
-            'params' => [
-                'prefix' => 'Admin',
-                'controller' => 'Posts',
-                'action' => 'index',
-                'pass' => [],
-                'return' => 1
-            ]
-        ]);
-        $response = $this->getMock('Cake\Network\Response');
-
-        $this->dispatcher->dispatch($request, $response);
-
-        $this->assertInstanceOf(
-            'TestApp\Controller\Admin\PostsController',
-            $this->dispatcher->controller
-        );
-        $expected = '/admin/posts/index';
-        $this->assertSame($expected, $request->here);
-    }
-
-    /**
-     * test prefix dispatching in a plugin.
-     *
-     * @return void
-     */
-    public function testPrefixDispatchPlugin()
-    {
-        Plugin::load('TestPlugin');
-
-        $request = new Request([
-            'url' => 'admin/test_plugin/comments/index',
-            'params' => [
-                'plugin' => 'TestPlugin',
-                'prefix' => 'Admin',
-                'controller' => 'Comments',
-                'action' => 'index',
-                'pass' => [],
-                'return' => 1
-            ]
-        ]);
-        $response = $this->getMock('Cake\Network\Response');
-
-        $this->dispatcher->dispatch($request, $response);
-
-        $this->assertInstanceOf(
-            'TestPlugin\Controller\Admin\CommentsController',
-            $this->dispatcher->controller
-        );
-    }
-
-    /**
      * test forbidden controller names.
      * test forbidden controller names.
      *
      *
      * @expectedException \Cake\Routing\Exception\MissingControllerException
      * @expectedException \Cake\Routing\Exception\MissingControllerException

+ 7 - 0
tests/TestCase/Routing/RequestActionTraitTest.php

@@ -73,29 +73,36 @@ class RequestActionTraitTest extends TestCase
 
 
         $result = $this->object->requestAction('');
         $result = $this->object->requestAction('');
         $this->assertFalse($result);
         $this->assertFalse($result);
+        $this->assertNull(Router::getRequest(), 'requests were not popped off the stack, this will break url generation');
 
 
         $result = $this->object->requestAction('/request_action/test_request_action');
         $result = $this->object->requestAction('/request_action/test_request_action');
         $expected = 'This is a test';
         $expected = 'This is a test';
         $this->assertEquals($expected, $result);
         $this->assertEquals($expected, $result);
+        $this->assertNull(Router::getRequest(), 'requests were not popped off the stack, this will break url generation');
 
 
         $result = $this->object->requestAction(Configure::read('App.fullBaseUrl') . '/request_action/test_request_action');
         $result = $this->object->requestAction(Configure::read('App.fullBaseUrl') . '/request_action/test_request_action');
         $expected = 'This is a test';
         $expected = 'This is a test';
         $this->assertEquals($expected, $result);
         $this->assertEquals($expected, $result);
+        $this->assertNull(Router::getRequest(), 'requests were not popped off the stack, this will break url generation');
 
 
         $result = $this->object->requestAction('/request_action/another_ra_test/2/5');
         $result = $this->object->requestAction('/request_action/another_ra_test/2/5');
         $expected = 7;
         $expected = 7;
         $this->assertEquals($expected, $result);
         $this->assertEquals($expected, $result);
+        $this->assertNull(Router::getRequest(), 'requests were not popped off the stack, this will break url generation');
 
 
         $result = $this->object->requestAction('/tests_apps/index', ['return']);
         $result = $this->object->requestAction('/tests_apps/index', ['return']);
         $expected = 'This is the TestsAppsController index view ';
         $expected = 'This is the TestsAppsController index view ';
         $this->assertEquals($expected, $result);
         $this->assertEquals($expected, $result);
+        $this->assertNull(Router::getRequest(), 'requests were not popped off the stack, this will break url generation');
 
 
         $result = $this->object->requestAction('/tests_apps/some_method');
         $result = $this->object->requestAction('/tests_apps/some_method');
         $expected = 5;
         $expected = 5;
         $this->assertEquals($expected, $result);
         $this->assertEquals($expected, $result);
+        $this->assertNull(Router::getRequest(), 'requests were not popped off the stack, this will break url generation');
 
 
         $result = $this->object->requestAction('/request_action/paginate_request_action');
         $result = $this->object->requestAction('/request_action/paginate_request_action');
         $this->assertNull($result);
         $this->assertNull($result);
+        $this->assertNull(Router::getRequest(), 'requests were not popped off the stack, this will break url generation');
 
 
         $result = $this->object->requestAction('/request_action/normal_request_action');
         $result = $this->object->requestAction('/request_action/normal_request_action');
         $expected = 'Hello World';
         $expected = 'Hello World';

+ 2 - 0
tests/TestCase/TestSuite/IntegrationTestCaseTest.php

@@ -181,7 +181,9 @@ class IntegrationTestCaseTest extends IntegrationTestCase
     {
     {
         $this->post('/posts/index');
         $this->post('/posts/index');
         $this->assertInstanceOf('Cake\Controller\Controller', $this->_controller);
         $this->assertInstanceOf('Cake\Controller\Controller', $this->_controller);
+        $this->assertNotEmpty($this->_viewName, 'View name not set');
         $this->assertContains('Template' . DS . 'Posts' . DS . 'index.ctp', $this->_viewName);
         $this->assertContains('Template' . DS . 'Posts' . DS . 'index.ctp', $this->_viewName);
+        $this->assertNotEmpty($this->_layoutName, 'Layout name not set');
         $this->assertContains('Template' . DS . 'Layout' . DS . 'default.ctp', $this->_layoutName);
         $this->assertContains('Template' . DS . 'Layout' . DS . 'default.ctp', $this->_layoutName);
 
 
         $this->assertTemplate('index');
         $this->assertTemplate('index');