Browse Source

Add standalone ControllerFactory.

Having this logic out of the a dispatcher filter is necessary for PSR7
migration where the controller factory acts as part of the
ActionDispatcher.
Mark Story 10 years ago
parent
commit
71ac75fac2

+ 2 - 1
composer.json

@@ -47,11 +47,12 @@
     "autoload-dev": {
         "psr-4": {
             "Cake\\Test\\": "tests",
-            "Company\\TestPluginThree\\Test\\": "tests/test_app/Plugin/Company/TestPluginThree/tests",
             "TestApp\\": "tests/test_app/TestApp",
             "TestPlugin\\": "tests/test_app/Plugin/TestPlugin/src",
             "TestPlugin\\Test\\": "tests/test_app/Plugin/TestPlugin/tests",
             "TestPluginTwo\\": "tests/test_app/Plugin/TestPluginTwo/src",
+            "Company\\TestPluginThree\\": "tests/test_app/Plugin/Company/TestPluginThree/src",
+            "Company\\TestPluginThree\\Test\\": "tests/test_app/Plugin/Company/TestPluginThree/tests",
             "PluginJs\\": "tests/test_app/Plugin/PluginJs/src"
         }
     },

+ 94 - 0
src/Http/ControllerFactory.php

@@ -0,0 +1,94 @@
+<?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.3.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Http;
+
+use Cake\Core\App;
+use Cake\Network\Request;
+use Cake\Network\Response;
+use Cake\Routing\Exception\MissingControllerException;
+use Cake\Utility\Inflector;
+use ReflectionClass;
+
+/**
+ * Factory method for building controllers from request/response pairs.
+ */
+class ControllerFactory
+{
+    /**
+     * Create a controller for a given request/response
+     *
+     * @param \Cake\Network\Request $request The request to build a controller for.
+     * @param \Cake\Network\Response $response The response to use.
+     * @return \Cake\Controller\Controller
+     */
+    public function create(Request $request, Response $response)
+    {
+        $pluginPath = $controller = null;
+        $namespace = 'Controller';
+        if (isset($request->params['plugin'])) {
+            $pluginPath = $request->params['plugin'] . '.';
+        }
+        if (isset($request->params['controller'])) {
+            $controller = $request->params['controller'];
+        }
+        if (isset($request->params['prefix'])) {
+            if (strpos($request->params['prefix'], '/') === false) {
+                $namespace .= '/' . Inflector::camelize($request->params['prefix']);
+            } else {
+                $prefixes = array_map(
+                    'Cake\Utility\Inflector::camelize',
+                    explode('/', $request->params['prefix'])
+                );
+                $namespace .= '/' . implode('/', $prefixes);
+            }
+        }
+        $firstChar = substr($controller, 0, 1);
+        if (strpos($controller, '\\') !== false ||
+            strpos($controller, '.') !== false ||
+            $firstChar === strtolower($firstChar)
+        ) {
+            return $this->missingController($request);
+        }
+        $className = false;
+        if ($pluginPath . $controller) {
+            $className = App::classname($pluginPath . $controller, $namespace, 'Controller');
+        }
+        if (!$className) {
+            return $this->missingController($request);
+        }
+        $reflection = new ReflectionClass($className);
+        if ($reflection->isAbstract() || $reflection->isInterface()) {
+            return $this->missingController($request);
+        }
+        return $reflection->newInstance($request, $response, $controller);
+    }
+
+    /**
+     * Throws an exception when a controller is missing.
+     *
+     * @param \Cake\Network\Request $request The request.
+     * @throws \Cake\Routing\Exception\MissingControllerException
+     * @return void
+     */
+    protected function missingController($request)
+    {
+        throw new MissingControllerException([
+            'class' => $request->param('controller'),
+            'plugin' => $request->param('plugin'),
+            'prefix' => $request->param('prefix'),
+            '_ext' => $request->param('_ext')
+        ]);
+    }
+}

+ 249 - 0
tests/TestCase/Http/ControllerFactoryTest.php

@@ -0,0 +1,249 @@
+<?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.3.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\TestCase\Http;
+
+use Cake\Core\Configure;
+use Cake\Http\ControllerFactory;
+use Cake\Network\Request;
+use Cake\Network\Response;
+use Cake\TestSuite\TestCase;
+
+/**
+ * Test case for ControllerFactory.
+ */
+class ControllerFactoryTest extends TestCase
+{
+    /**
+     * Setup
+     *
+     * @return void
+     */
+    public function setUp()
+    {
+        parent::setUp();
+        Configure::write('App.namespace', 'TestApp');
+        $this->factory = new ControllerFactory();
+        $this->response = $this->getMock('Cake\Network\Response');
+    }
+
+    /**
+     * Test building an application controller
+     *
+     * @return void
+     */
+    public function testApplicationController()
+    {
+        $request = new Request([
+            'url' => 'cakes/index',
+            'params' => [
+                'controller' => 'Cakes',
+                'action' => 'index',
+            ]
+        ]);
+        $result = $this->factory->create($request, $this->response);
+        $this->assertInstanceOf('TestApp\Controller\CakesController', $result);
+        $this->assertSame($request, $result->request);
+        $this->assertSame($this->response, $result->response);
+    }
+
+    /**
+     * Test building a prefixed app controller.
+     *
+     * @return void
+     */
+    public function testPrefixedAppController()
+    {
+        $request = new Request([
+            'url' => 'admin/posts/index',
+            'params' => [
+                'prefix' => 'admin',
+                'controller' => 'Posts',
+                'action' => 'index',
+            ]
+        ]);
+        $result = $this->factory->create($request, $this->response);
+        $this->assertInstanceOf(
+            'TestApp\Controller\Admin\PostsController',
+            $result
+        );
+        $this->assertSame($request, $result->request);
+        $this->assertSame($this->response, $result->response);
+    }
+
+    /**
+     * Test building a nested prefix app controller
+     *
+     * @return void
+     */
+    public function testNestedPrefixedAppController()
+    {
+        $request = new Request([
+            'url' => 'admin/sub/posts/index',
+            'params' => [
+                'prefix' => 'admin/sub',
+                'controller' => 'Posts',
+                'action' => 'index',
+            ]
+        ]);
+        $result = $this->factory->create($request, $this->response);
+        $this->assertInstanceOf(
+            'TestApp\Controller\Admin\Sub\PostsController',
+            $result
+        );
+        $this->assertSame($request, $result->request);
+        $this->assertSame($this->response, $result->response);
+    }
+
+    /**
+     * Test building a plugin controller
+     *
+     * @return void
+     */
+    public function testPluginController()
+    {
+        $request = new Request([
+            'url' => 'test_plugin/test_plugin/index',
+            'params' => [
+                'plugin' => 'TestPlugin',
+                'controller' => 'TestPlugin',
+                'action' => 'index',
+            ]
+        ]);
+        $result = $this->factory->create($request, $this->response);
+        $this->assertInstanceOf(
+            'TestPlugin\Controller\TestPluginController',
+            $result
+        );
+        $this->assertSame($request, $result->request);
+        $this->assertSame($this->response, $result->response);
+    }
+
+    /**
+     * Test building a vendored plugin controller.
+     *
+     * @return void
+     */
+    public function testVendorPluginController()
+    {
+        $request = new Request([
+            'url' => 'test_plugin_three/ovens/index',
+            'params' => [
+                'plugin' => 'Company/TestPluginThree',
+                'controller' => 'Ovens',
+                'action' => 'index',
+            ]
+        ]);
+        $result = $this->factory->create($request, $this->response);
+        $this->assertInstanceOf(
+            'Company\TestPluginThree\Controller\OvensController',
+            $result
+        );
+        $this->assertSame($request, $result->request);
+        $this->assertSame($this->response, $result->response);
+    }
+
+    /**
+     * Test building a prefixed plugin controller
+     *
+     * @return void
+     */
+    public function testPrefixedPluginController()
+    {
+        $request = new Request([
+            'url' => 'test_plugin/admin/comments',
+            'params' => [
+                'prefix' => 'admin',
+                'plugin' => 'TestPlugin',
+                'controller' => 'Comments',
+                'action' => 'index',
+            ]
+        ]);
+        $result = $this->factory->create($request, $this->response);
+        $this->assertInstanceOf(
+            'TestPlugin\Controller\Admin\CommentsController',
+            $result
+        );
+        $this->assertSame($request, $result->request);
+        $this->assertSame($this->response, $result->response);
+    }
+
+    /**
+     * @expectedException \Cake\Routing\Exception\MissingControllerException
+     * @expectedExceptionMessage Controller class Abstract could not be found.
+     * @return void
+     */
+    public function testAbstractClassFailure()
+    {
+        $request = new Request([
+            'url' => 'abstract/index',
+            'params' => [
+                'controller' => 'Abstract',
+                'action' => 'index',
+            ]
+        ]);
+        $this->factory->create($request, $this->response);
+    }
+
+    /**
+     * @expectedException \Cake\Routing\Exception\MissingControllerException
+     * @expectedExceptionMessage Controller class Interface could not be found.
+     * @return void
+     */
+    public function testInterfaceFailure()
+    {
+        $request = new Request([
+            'url' => 'interface/index',
+            'params' => [
+                'controller' => 'Interface',
+                'action' => 'index',
+            ]
+        ]);
+        $this->factory->create($request, $this->response);
+    }
+
+    /**
+     * @expectedException \Cake\Routing\Exception\MissingControllerException
+     * @expectedExceptionMessage Controller class Invisible could not be found.
+     * @return void
+     */
+    public function testMissingClassFailure()
+    {
+        $request = new Request([
+            'url' => 'interface/index',
+            'params' => [
+                'controller' => 'Invisible',
+                'action' => 'index',
+            ]
+        ]);
+        $this->factory->create($request, $this->response);
+    }
+
+    /**
+     * @expectedException \Cake\Routing\Exception\MissingControllerException
+     * @expectedExceptionMessage Controller class TestApp\Controller\CakesController could not be found.
+     * @return void
+     */
+    public function testAbsoluteReferenceFailure()
+    {
+        $request = new Request([
+            'url' => 'interface/index',
+            'params' => [
+                'controller' => 'TestApp\Controller\CakesController',
+                'action' => 'index',
+            ]
+        ]);
+        $this->factory->create($request, $this->response);
+    }
+}

+ 12 - 0
tests/test_app/Plugin/Company/TestPluginThree/src/Controller/OvensController.php

@@ -0,0 +1,12 @@
+<?php
+namespace Company\TestPluginThree\Controller;
+
+use Cake\Controller\Controller;
+
+class OvensController extends Controller
+{
+    public function index()
+    {
+        $this->autoRender = false;
+    }
+}

+ 39 - 0
tests/test_app/TestApp/Controller/CakesController.php

@@ -0,0 +1,39 @@
+<?php
+namespace TestApp\Controller;
+
+use Cake\Controller\Controller;
+use Cake\Network\Exception\NotFoundException;
+
+/**
+ * CakesController class
+ */
+class CakesController extends Controller
+{
+    /**
+     * The default model to use.
+     *
+     * @var string
+     */
+    public $modelClass = 'Posts';
+
+    /**
+     * index method
+     *
+     * @return \Cake\Network\Response
+     */
+    public function index()
+    {
+        $this->response->body('Hello Jane');
+        return $this->response;
+    }
+
+    /**
+     * invalid method
+     *
+     * @return \Cake\Network\Response
+     */
+    public function invalid()
+    {
+        return 'Some string';
+    }
+}

+ 8 - 0
tests/test_app/TestApp/Controller/InterfaceController.php

@@ -0,0 +1,8 @@
+<?php
+namespace TestApp\Controller;
+
+interface InterfaceController
+{
+
+    public function index();
+}