From 03744a2b676b5c34dc67750b368c7475909c4581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Gonz=C3=A1lez?= Date: Tue, 27 Feb 2018 12:54:41 +0000 Subject: [PATCH] refs #10778 add router cache --- src/Routing/Middleware/RoutingMiddleware.php | 50 +++++++++++ src/Routing/Router.php | 12 +++ .../Middleware/RoutingMiddlewareTest.php | 87 +++++++++++++++++++ 3 files changed, 149 insertions(+) diff --git a/src/Routing/Middleware/RoutingMiddleware.php b/src/Routing/Middleware/RoutingMiddleware.php index e9e7b9f8f50..c4d1db88b58 100644 --- a/src/Routing/Middleware/RoutingMiddleware.php +++ b/src/Routing/Middleware/RoutingMiddleware.php @@ -14,6 +14,9 @@ */ namespace Cake\Routing\Middleware; +======= +use Cake\Cache\Cache; +use Cake\Core\Configure; use Cake\Core\PluginApplicationInterface; use Cake\Http\BaseApplication; use Cake\Http\MiddlewareQueue; @@ -30,6 +33,16 @@ */ class RoutingMiddleware { + /** + * Name of the default cache configuration name used to store routes collection + */ + const DEFAULT_ROUTER_CACHE_CONFIG = '_cake_router_'; + + /** + * Key used to store the route collection in the cache engine + */ + const ROUTE_COLLECTION_CACHE_KEY = 'routeCollection'; + /** * The application that will have its routing hook invoked. * @@ -49,6 +62,7 @@ public function __construct(BaseApplication $app = null) /** * Trigger the application's routes() hook if the application exists and Router isn't initialized. + * Uses the routes cache if enabled via configuration param "Router.cache" * * If the middleware is created without an Application, routes will be * loaded via the automatic route loading that pre-dates the routes() hook. @@ -60,11 +74,46 @@ protected function loadRoutes() if (!$this->app) { return; } + $routeCollection = $this->buildRouteCollection(); + Router::setRouteCollection($routeCollection); + // Prevent routes from being loaded again + Router::$initialized = true; + } + + /** + * Check if route cache is enabled and use the configured Cache to 'remember' the route collection + * + * @return \Cake\Routing\RouteCollection + */ + protected function buildRouteCollection() + { + $isRouterCacheEnabled = Configure::read('Router.cache'); + if (Cache::enabled() && $isRouterCacheEnabled) { + $routesCacheConfig = Configure::read('Router.cacheConfig', self::DEFAULT_ROUTER_CACHE_CONFIG); + + return Cache::remember(self::ROUTE_COLLECTION_CACHE_KEY, function () { + + return $this->prepareRouteCollection(); + }, $routesCacheConfig); + } + + return $this->prepareRouteCollection(); + } + + /** + * Generate the route collection using the builder + * + * @return \Cake\Routing\RouteCollection + */ + protected function prepareRouteCollection() + { $builder = Router::createRouteBuilder('/'); $this->app->routes($builder); if ($this->app instanceof PluginApplicationInterface) { $this->app->pluginRoutes($builder); } + + return Router::getRouteCollection(); } /** @@ -77,6 +126,7 @@ protected function loadRoutes() * @param \Psr\Http\Message\ResponseInterface $response The response. * @param callable $next The next middleware to call. * @return \Psr\Http\Message\ResponseInterface A response. + * @throws \Cake\Routing\InvalidArgumentException */ public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next) { diff --git a/src/Routing/Router.php b/src/Routing/Router.php index 1dd0af5138a..991b74af387 100644 --- a/src/Routing/Router.php +++ b/src/Routing/Router.php @@ -1148,6 +1148,17 @@ public static function getRouteCollection() return static::$_collection; } + /** + * Set the RouteCollection inside the Router + * + * @param RouteCollection $routeCollection + * @return void + */ + public static function setRouteCollection($routeCollection) + { + static::$_collection = $routeCollection; + } + /** * Loads route configuration * @@ -1159,4 +1170,5 @@ protected static function _loadRoutes() static::$initialized = true; include CONFIG . 'routes.php'; } + } diff --git a/tests/TestCase/Routing/Middleware/RoutingMiddlewareTest.php b/tests/TestCase/Routing/Middleware/RoutingMiddlewareTest.php index 9da9d09c335..8a574f1cdc6 100644 --- a/tests/TestCase/Routing/Middleware/RoutingMiddlewareTest.php +++ b/tests/TestCase/Routing/Middleware/RoutingMiddlewareTest.php @@ -14,6 +14,8 @@ */ namespace Cake\Test\TestCase\Routing\Middleware; +use Cake\Cache\Cache; +use Cake\Core\Configure; use Cake\Routing\Middleware\RoutingMiddleware; use Cake\Routing\RouteBuilder; use Cake\Routing\Router; @@ -454,4 +456,89 @@ public function scopedMiddlewareUrlProvider() ['/api/version', ['second', 'last']], ]; } + + /** + * Test we store route collection in cache. + * + * @return void + */ + public function testCacheRoutes() + { + Configure::write('Router.cache', true); + Cache::setConfig('_cake_router_', [ + 'engine' => 'File', + 'path' => TMP, + ]); + Router::$initialized = false; + $request = ServerRequestFactory::fromGlobals(['REQUEST_URI' => '/articles']); + $response = new Response(); + $next = function ($req, $res) { + $routeCollection = Cache::read('routeCollection', '_cake_router_'); + $this->assertInstanceOf('\Cake\Routing\RouteCollection', $routeCollection); + return $res; + }; + $app = new Application(CONFIG); + $middleware = new RoutingMiddleware($app); + $middleware($request, $response, $next); + + Cache::clear(false, '_cake_router_'); + Cache::drop('_cake_router_'); + } + + /** + * Test we don't cache routes if cache is disabled. + * + * @return void + */ + public function testCacheNotUsedIfCacheDisabled() + { + Configure::write('Router.cache', true); + Cache::disable(); + Cache::setConfig('_cake_router_', [ + 'engine' => 'File', + 'path' => TMP, + ]); + Router::$initialized = false; + $request = ServerRequestFactory::fromGlobals(['REQUEST_URI' => '/articles']); + $response = new Response(); + $next = function ($req, $res) { + $routeCollection = Cache::read('routeCollection', '_cake_router_'); + $this->assertFalse($routeCollection); + return $res; + }; + $app = new Application(CONFIG); + $middleware = new RoutingMiddleware($app); + $middleware($request, $response, $next); + + Cache::drop('_cake_router_'); + Cache::enable(); + } + + /** + * Test cache name is used + * + * @return void + * @expectedException InvalidArgumentException + * @expectedExceptionMessage The "notfound" cache configuration does not exist + */ + public function testCacheConfigNotFound() + { + Configure::write('Router.cache', true); + Configure::write('Router.cacheConfig', 'notfound'); + Cache::setConfig('_cake_router_', [ + 'engine' => 'File', + 'path' => TMP, + ]); + Router::$initialized = false; + $request = ServerRequestFactory::fromGlobals(['REQUEST_URI' => '/articles']); + $response = new Response(); + $next = function ($req, $res) { + return $res; + }; + $app = new Application(CONFIG); + $middleware = new RoutingMiddleware($app); + $middleware($request, $response, $next); + + Cache::drop('_cake_router_'); + } }