Browse Source

Merge pull request #17639 from cakephp/5.1-app-plugin-collection

5.1 app plugin collection
Mark Story 2 years ago
parent
commit
00b3241dbd

+ 11 - 0
src/Core/Plugin.php

@@ -129,4 +129,15 @@ class Plugin
     {
         return static::$plugins ??= new PluginCollection();
     }
+
+    /**
+     * Set the shared plugin collection.
+     *
+     * @param \Cake\Core\PluginCollection $collection
+     * @return void
+     */
+    public static function setCollection(PluginCollection $collection): void
+    {
+        static::$plugins = $collection;
+    }
 }

+ 11 - 0
src/Core/TestSuite/ContainerStubTrait.php

@@ -20,6 +20,7 @@ use Cake\Core\ConsoleApplicationInterface;
 use Cake\Core\ContainerInterface;
 use Cake\Core\HttpApplicationInterface;
 use Cake\Event\EventInterface;
+use Cake\Routing\Router;
 use Closure;
 use League\Container\Exception\NotFoundException;
 use LogicException;
@@ -79,6 +80,8 @@ trait ContainerStubTrait
      */
     protected function createApp(): HttpApplicationInterface|ConsoleApplicationInterface
     {
+        Router::resetRoutes();
+
         if ($this->_appClass) {
             $appClass = $this->_appClass;
         } else {
@@ -95,6 +98,14 @@ trait ContainerStubTrait
             $app->getEventManager()->on('Application.buildContainer', [$this, 'modifyContainer']);
         }
 
+        foreach ($this->appPluginsToLoad as $pluginName => $config) {
+            if (is_array($config)) {
+                $app->addPlugin($pluginName, $config);
+            } else {
+                $app->addPlugin($config);
+            }
+        }
+
         return $app;
     }
 

+ 2 - 1
src/Http/BaseApplication.php

@@ -107,9 +107,10 @@ abstract class BaseApplication implements
         ?ControllerFactoryInterface $controllerFactory = null
     ) {
         $this->configDir = rtrim($configDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
-        $this->plugins = Plugin::getCollection();
+        $this->plugins = new PluginCollection();
         $this->_eventManager = $eventManager ?: EventManager::instance();
         $this->controllerFactory = $controllerFactory;
+        Plugin::setCollection($this->plugins);
     }
 
     /**

+ 52 - 1
src/TestSuite/IntegrationTestTrait.php

@@ -18,6 +18,7 @@ namespace Cake\TestSuite;
 use Cake\Controller\Controller;
 use Cake\Core\Configure;
 use Cake\Core\HttpApplicationInterface;
+use Cake\Core\PluginApplicationInterface;
 use Cake\Core\TestSuite\ContainerStubTrait;
 use Cake\Database\Exception\DatabaseException;
 use Cake\Error\Renderer\WebExceptionRenderer;
@@ -27,6 +28,7 @@ use Cake\Form\FormProtector;
 use Cake\Http\Middleware\CsrfProtectionMiddleware;
 use Cake\Http\Session;
 use Cake\Routing\Router;
+use Cake\Routing\RoutingApplicationInterface;
 use Cake\TestSuite\Constraint\Response\BodyContains;
 use Cake\TestSuite\Constraint\Response\BodyEmpty;
 use Cake\TestSuite\Constraint\Response\BodyEquals;
@@ -491,8 +493,8 @@ trait IntegrationTestTrait
      */
     protected function _sendRequest(array|string $url, string $method, array|string $data = []): void
     {
+        $url = $this->resolveUrl($url);
         $dispatcher = $this->_makeDispatcher();
-        $url = $dispatcher->resolveUrl($url);
 
         try {
             $request = $this->_buildRequest($url, $method, $data);
@@ -512,6 +514,55 @@ trait IntegrationTestTrait
     }
 
     /**
+     * Resolve the provided URL into a string.
+     *
+     * @param array|string $url The URL array/string to resolve.
+     * @return string
+     * @since 5.1.0
+     */
+    public function resolveUrl(array|string $url): string
+    {
+        // If we need to resolve a Route URL but there are no routes, load routes.
+        if (is_array($url) && count(Router::getRouteCollection()->routes()) === 0) {
+            return $this->resolveRoute($url);
+        }
+
+        return Router::url($url);
+    }
+
+    /**
+     * Convert a URL array into a string URL via routing.
+     *
+     * @param array $url The url to resolve
+     * @return string
+     * @since 5.1.0
+     */
+    protected function resolveRoute(array $url): string
+    {
+        $app = $this->createApp();
+
+        // Simulate application bootstrap and route loading.
+        // We need both to ensure plugins are loaded.
+        $app->bootstrap();
+        if ($app instanceof PluginApplicationInterface) {
+            $app->pluginBootstrap();
+        }
+        $builder = Router::createRouteBuilder('/');
+
+        if ($app instanceof RoutingApplicationInterface) {
+            $app->routes($builder);
+        }
+        if ($app instanceof PluginApplicationInterface) {
+            $app->pluginRoutes($builder);
+        }
+
+        $out = Router::url($url);
+        Router::resetRoutes();
+
+        return $out;
+    }
+
+    /**
      * Get the correct dispatcher instance.
      *
      * @return \Cake\TestSuite\MiddlewareDispatcher A dispatcher instance

+ 2 - 0
src/TestSuite/MiddlewareDispatcher.php

@@ -55,6 +55,7 @@ class MiddlewareDispatcher
      *
      * @param array|string $url The URL array/string to resolve.
      * @return string
+     * @deprecated 5.1.0 Use IntegrationTestTrait::resolveUrl() instead.
      */
     public function resolveUrl(array|string $url): string
     {
@@ -71,6 +72,7 @@ class MiddlewareDispatcher
      *
      * @param array $url The url to resolve
      * @return string
+     * @deprecated 5.1.0 Use IntegrationTestTrait::resolveRouter() instead.
      */
     protected function resolveRoute(array $url): string
     {

+ 9 - 0
src/TestSuite/TestCase.php

@@ -74,6 +74,13 @@ abstract class TestCase extends BaseTestCase
     protected array $_configure = [];
 
     /**
+     * Plugins to be loaded after app instance is created ContainerStubTrait::creatApp()
+     *
+     * @var array
+     */
+    protected array $appPluginsToLoad = [];
+
+    /**
      * @var \Cake\Error\PhpError|null
      */
     private ?PhpError $_capturedError = null;
@@ -309,6 +316,8 @@ abstract class TestCase extends BaseTestCase
      */
     public function loadPlugins(array $plugins = []): BaseApplication
     {
+        $this->appPluginsToLoad = $plugins;
+
         /**
          * @psalm-suppress MissingTemplateParam
          */

+ 26 - 22
tests/TestCase/Command/RoutesCommandTest.php

@@ -18,6 +18,7 @@ namespace Cake\Test\TestCase\Command;
 
 use Cake\Console\CommandInterface;
 use Cake\Console\TestSuite\ConsoleIntegrationTestTrait;
+use Cake\Core\Configure;
 use Cake\Routing\Route\Route;
 use Cake\Routing\Router;
 use Cake\TestSuite\TestCase;
@@ -139,9 +140,11 @@ class RoutesCommandTest extends TestCase
      */
     public function testRouteListSorted(): void
     {
-        Router::createRouteBuilder('/')->connect(
-            new Route('/a/route/sorted', [], ['_name' => '_aRoute'])
-        );
+        Configure::write('TestApp.routes', function ($routes) {
+            $routes->connect(
+                new Route('/a/route/sorted', [], ['_name' => '_aRoute'])
+            );
+        });
 
         $this->exec('routes -s');
         $this->assertExitCode(CommandInterface::CODE_SUCCESS);
@@ -311,27 +314,28 @@ class RoutesCommandTest extends TestCase
      */
     public function testRouteDuplicateWarning(): void
     {
-        $builder = Router::createRouteBuilder('/');
-        $builder->connect(
-            new Route('/unique-path', [], ['_name' => '_aRoute'])
-        );
-        $builder->connect(
-            new Route('/unique-path', [], ['_name' => '_bRoute'])
-        );
+        Configure::write('TestApp.routes', function ($builder) {
+            $builder->connect(
+                new Route('/unique-path', [], ['_name' => '_aRoute'])
+            );
+            $builder->connect(
+                new Route('/unique-path', [], ['_name' => '_bRoute'])
+            );
 
-        $builder->connect(
-            new Route('/blog', ['_method' => 'GET'], ['_name' => 'blog-get'])
-        );
-        $builder->connect(
-            new Route('/blog', [], ['_name' => 'blog-all'])
-        );
+            $builder->connect(
+                new Route('/blog', ['_method' => 'GET'], ['_name' => 'blog-get'])
+            );
+            $builder->connect(
+                new Route('/blog', [], ['_name' => 'blog-all'])
+            );
 
-        $builder->connect(
-            new Route('/events', ['_method' => ['POST', 'PUT']], ['_name' => 'events-post'])
-        );
-        $builder->connect(
-            new Route('/events', ['_method' => 'GET'], ['_name' => 'events-get'])
-        );
+            $builder->connect(
+                new Route('/events', ['_method' => ['POST', 'PUT']], ['_name' => 'events-post'])
+            );
+            $builder->connect(
+                new Route('/events', ['_method' => 'GET'], ['_name' => 'events-get'])
+            );
+        });
 
         $this->exec('routes');
         $this->assertExitCode(CommandInterface::CODE_SUCCESS);

+ 1 - 13
tests/TestCase/Console/Command/HelpCommandTest.php

@@ -18,9 +18,6 @@ namespace Cake\Test\TestCase\Console\Command;
 
 use Cake\Console\CommandInterface;
 use Cake\Console\TestSuite\ConsoleIntegrationTestTrait;
-use Cake\Core\Plugin;
-use Cake\Http\BaseApplication;
-use Cake\Http\MiddlewareQueue;
 use Cake\TestSuite\TestCase;
 
 /**
@@ -37,16 +34,7 @@ class HelpCommandTest extends TestCase
     {
         parent::setUp();
         $this->setAppNamespace();
-        Plugin::getCollection()->clear();
-
-        $app = new class ('') extends BaseApplication
-        {
-            public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
-            {
-                return $middlewareQueue;
-            }
-        };
-        $app->addPlugin('TestPlugin');
+        $this->loadPlugins(['TestPlugin']);
     }
 
     /**

+ 1 - 1
tests/TestCase/Routing/AssetTest.php

@@ -229,7 +229,7 @@ class AssetTest extends TestCase
     public function testAssetTimestampPluginsAndThemes(): void
     {
         Configure::write('Asset.timestamp', 'force');
-        $this->loadPlugins(['TestPlugin', 'Company/TestPluginThree']);
+        $this->loadPlugins(['TestTheme', 'TestPlugin', 'Company/TestPluginThree']);
 
         $result = Asset::assetTimestamp('/test_plugin/css/test_plugin_asset.css');
         $this->assertMatchesRegularExpression('#/test_plugin/css/test_plugin_asset.css\?[0-9]+$#', $result, 'Missing timestamp plugin');

+ 52 - 46
tests/TestCase/TestSuite/IntegrationTestTraitTest.php

@@ -54,11 +54,6 @@ class IntegrationTestTraitTest extends TestCase
     protected $key = 'abcdabcdabcdabcdabcdabcdabcdabcdabcd';
 
     /**
-     * @var \Cake\Routing\RouteBuilder
-     */
-    protected $builder;
-
-    /**
      * Setup method
      */
     public function setUp(): void
@@ -67,27 +62,30 @@ class IntegrationTestTraitTest extends TestCase
         static::setAppNamespace();
 
         Router::reload();
-        $this->builder = Router::createRouteBuilder('/');
-        $this->builder->setExtensions(['json']);
-        $this->builder->registerMiddleware('cookie', new EncryptedCookieMiddleware(['secrets'], $this->key));
-        $this->builder->applyMiddleware('cookie');
-
-        $this->builder->setRouteClass(InflectedRoute::class);
-        $this->builder->get('/get/{controller}/{action}', []);
-        $this->builder->head('/head/{controller}/{action}', []);
-        $this->builder->options('/options/{controller}/{action}', []);
-        $this->builder->connect('/{controller}/{action}/*', []);
-
-        $this->builder->scope('/cookie-csrf/', ['csrf' => 'cookie'], function (RouteBuilder $routes): void {
-            $routes->registerMiddleware('cookieCsrf', new CsrfProtectionMiddleware());
-            $routes->applyMiddleware('cookieCsrf');
-            $routes->connect('/posts/{action}', ['controller' => 'Posts']);
-        });
-        $this->builder->scope('/session-csrf/', ['csrf' => 'session'], function (RouteBuilder $routes): void {
-            $routes->registerMiddleware('sessionCsrf', new SessionCsrfProtectionMiddleware());
-            $routes->applyMiddleware('sessionCsrf');
-            $routes->connect('/posts/{action}/', ['controller' => 'Posts']);
-        });
+        $routesClosure = function (RouteBuilder $routes) {
+            $routes->setExtensions(['json']);
+            $routes->registerMiddleware('cookie', new EncryptedCookieMiddleware(['secrets'], $this->key));
+            $routes->applyMiddleware('cookie');
+
+            $routes->setRouteClass(InflectedRoute::class);
+            $routes->get('/get/{controller}/{action}', []);
+            $routes->head('/head/{controller}/{action}', []);
+            $routes->options('/options/{controller}/{action}', []);
+            $routes->connect('/{controller}/{action}/*', []);
+
+            $routes->scope('/cookie-csrf/', ['csrf' => 'cookie'], function (RouteBuilder $routes): void {
+                $routes->registerMiddleware('cookieCsrf', new CsrfProtectionMiddleware());
+                $routes->applyMiddleware('cookieCsrf');
+                $routes->connect('/posts/{action}', ['controller' => 'Posts']);
+            });
+            $routes->scope('/session-csrf/', ['csrf' => 'session'], function (RouteBuilder $routes): void {
+                $routes->registerMiddleware('sessionCsrf', new SessionCsrfProtectionMiddleware());
+                $routes->applyMiddleware('sessionCsrf');
+                $routes->connect('/posts/{action}/', ['controller' => 'Posts']);
+            });
+        };
+        $routesClosure(Router::createRouteBuilder('/'));
+        Configure::write('TestApp.routes', $routesClosure);
 
         $this->configApplication(Configure::read('App.namespace') . '\Application', null);
     }
@@ -312,10 +310,12 @@ class IntegrationTestTraitTest extends TestCase
     public function testExceptionsInMiddlewareJsonView(): void
     {
         Router::reload();
-        $this->builder->connect('/json_response/api_get_data', [
-            'controller' => 'JsonResponse',
-            'action' => 'apiGetData',
-        ]);
+        Configure::write('TestApp.routes', function (RouteBuilder $routes) {
+            $routes->connect('/json_response/api_get_data', [
+                'controller' => 'JsonResponse',
+                'action' => 'apiGetData',
+            ]);
+        });
 
         $this->configApplication(Configure::read('App.namespace') . '\ApplicationWithExceptionsInMiddleware', null);
 
@@ -1030,15 +1030,18 @@ class IntegrationTestTraitTest extends TestCase
      */
     public function testPostSessionCsrfSuccessWithSetCookieName(): void
     {
-        $this->builder->scope('/custom-cookie-csrf/', ['csrf' => 'cookie'], function (RouteBuilder $routes): void {
-            $routes->registerMiddleware('cookieCsrf', new CsrfProtectionMiddleware(
-                [
-                    'cookieName' => 'customCsrfToken',
-                ]
-            ));
-            $routes->applyMiddleware('cookieCsrf');
-            $routes->connect('/posts/{action}', ['controller' => 'Posts']);
+        Configure::write('TestApp.routes', function (RouteBuilder $routes) {
+            $routes->scope('/custom-cookie-csrf/', ['csrf' => 'cookie'], function (RouteBuilder $routes): void {
+                $routes->registerMiddleware('cookieCsrf', new CsrfProtectionMiddleware(
+                    [
+                        'cookieName' => 'customCsrfToken',
+                    ]
+                ));
+                $routes->applyMiddleware('cookieCsrf');
+                $routes->connect('/posts/{action}', ['controller' => 'Posts']);
+            });
         });
+
         $this->enableCsrfToken('customCsrfToken');
         $data = [
             'title' => 'Some title',
@@ -1053,15 +1056,18 @@ class IntegrationTestTraitTest extends TestCase
      */
     public function testPostSessionCsrfFailureWithSetCookieName(): void
     {
-        $this->builder->scope('/custom-cookie-csrf/', ['csrf' => 'cookie'], function (RouteBuilder $routes): void {
-            $routes->registerMiddleware('cookieCsrf', new CsrfProtectionMiddleware(
-                [
-                    'cookieName' => 'customCsrfToken',
-                ]
-            ));
-            $routes->applyMiddleware('cookieCsrf');
-            $routes->connect('/posts/{action}', ['controller' => 'Posts']);
+        Configure::write('TestApp.routes', function (RouteBuilder $routes) {
+            $routes->scope('/custom-cookie-csrf/', ['csrf' => 'cookie'], function (RouteBuilder $routes): void {
+                $routes->registerMiddleware('cookieCsrf', new CsrfProtectionMiddleware(
+                    [
+                        'cookieName' => 'customCsrfToken',
+                    ]
+                ));
+                $routes->applyMiddleware('cookieCsrf');
+                $routes->connect('/posts/{action}', ['controller' => 'Posts']);
+            });
         });
+
         $this->enableCsrfToken('customCsrfToken');
         $data = [
             'title' => 'Some title',

+ 1 - 1
tests/TestCase/View/Helper/UrlHelperTest.php

@@ -333,7 +333,7 @@ class UrlHelperTest extends TestCase
     public function testAssetTimestampPluginsAndThemes(): void
     {
         Configure::write('Asset.timestamp', 'force');
-        $this->loadPlugins(['TestPlugin']);
+        $this->loadPlugins(['TestTheme', 'TestPlugin']);
 
         $result = $this->Helper->assetTimestamp('/test_plugin/css/test_plugin_asset.css');
         $this->assertMatchesRegularExpression('#/test_plugin/css/test_plugin_asset.css\?[0-9]+$#', $result, 'Missing timestamp plugin');

+ 6 - 0
tests/test_app/TestApp/Application.php

@@ -74,6 +74,12 @@ class Application extends BaseApplication
      */
     public function routes(RouteBuilder $routes): void
     {
+        // Additional routes to load
+        if (Configure::check('TestApp.routes')) {
+            $func = Configure::read('TestApp.routes');
+            $func($routes);
+        }
+
         $routes->registerMiddleware('dumb', new DumbMiddleware());
         $routes->registerMiddleware('sample', new SampleMiddleware());
         $routes->scope('/app', function (RouteBuilder $routes): void {