From 648da8c108ff7511b9d7e6176252e0d7b4d79377 Mon Sep 17 00:00:00 2001 From: agis Date: Wed, 8 Jul 2026 14:29:10 +0700 Subject: [PATCH 1/4] ignore .devenv --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index cf3b0654..68eee822 100755 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ Thumbs.db .idea/ .phpunit.result.cache .direnv -.envrc \ No newline at end of file +.envrc +.devenv \ No newline at end of file From 8c2bed3ab3257110e8b1d18fbd7d87cfcc47a313 Mon Sep 17 00:00:00 2001 From: agis Date: Wed, 8 Jul 2026 14:52:39 +0700 Subject: [PATCH 2/4] copy files from MaartenStaa/laravel-41-route-caching --- src/Illuminate/CachedRouting/Route.php | 103 +++++ .../CachedRouting/RouteCollection.php | 174 ++++++++ src/Illuminate/CachedRouting/Router.php | 246 +++++++++++ .../CachedRouting/RoutingServiceProvider.php | 74 ++++ tests/CachedRouting/IntegrationTest.php | 403 ++++++++++++++++++ .../RoutingServiceProviderTest.php | 98 +++++ 6 files changed, 1098 insertions(+) create mode 100644 src/Illuminate/CachedRouting/Route.php create mode 100644 src/Illuminate/CachedRouting/RouteCollection.php create mode 100644 src/Illuminate/CachedRouting/Router.php create mode 100644 src/Illuminate/CachedRouting/RoutingServiceProvider.php create mode 100755 tests/CachedRouting/IntegrationTest.php create mode 100644 tests/CachedRouting/RoutingServiceProviderTest.php diff --git a/src/Illuminate/CachedRouting/Route.php b/src/Illuminate/CachedRouting/Route.php new file mode 100644 index 00000000..385466d5 --- /dev/null +++ b/src/Illuminate/CachedRouting/Route.php @@ -0,0 +1,103 @@ +routingToController($this->action) === true) { + $this->action = $app['router']->makeControllerActionClosure($this->action); + } + + return parent::run(); + } + + /** + * Determines whether routes should be compiled. Caches the value at a class + * level. + * + * @return bool + */ + protected function shouldCompile() + { + if (static::$shouldCompileRoute === null) { + // If the compiled variable exists, we should compile. + static::$shouldCompileRoute = array_key_exists('compiled', get_object_vars($this)); + } + + return static::$shouldCompileRoute; + } + + /** + * Compile the route into a Symfony CompiledRoute instance. + */ + protected function compileRoute() + { + if ($this->shouldCompile() === true && $this->compiled === null) { + parent::compileRoute(); + } + } + + /** + * Prepare to go to sleep. Compiles the route so that doesn't have to happen + * on every subsequent request. + * + * @return array + */ + public function __sleep() + { + $this->compileRoute(); + + return array_keys(get_object_vars($this)); + } +} diff --git a/src/Illuminate/CachedRouting/RouteCollection.php b/src/Illuminate/CachedRouting/RouteCollection.php new file mode 100644 index 00000000..2f6f4fcd --- /dev/null +++ b/src/Illuminate/CachedRouting/RouteCollection.php @@ -0,0 +1,174 @@ +backup; + } + + /** + * Save the current collection of routes to the backup to start with an empty + * collection to cache. + */ + public function saveRouteCollection() + { + $this->backup = $this->getCacheableRouteContents(); + + foreach (array_keys($this->backup) as $k) { + $this->$k = array(); + } + } + + /** + * Restore the backed up routes. + */ + public function restoreRouteCollection() + { + $this->restoreRoutes($this->getBackup(), true); + } + + /** + * Get the routes in a form that can be saved to the cache. + * + * @return string + */ + public function getCacheableRoutes() + { + return serialize($this->getCacheableRouteContents()); + } + + /** + * Get the data that should be sent to the cache. + * + * @return array + */ + public function getCacheableRouteContents() + { + return array_except(get_object_vars($this), array('backup')); + } + + /** + * Restore a set of cached routes into this collection. + * + * @param string $cache + */ + public function restoreRouteCache($cache) + { + $routes = unserialize($cache); + + $this->restoreRoutes($routes); + } + + /** + * Add a set of routes back into this collection. + * + * @param bool $prepend + * @param array $routes + */ + protected function restoreRoutes($routes, $prepend = false) + { + foreach ($routes as $k => $v) { + if ($k === 'routes') { + $this->$k = $prepend === false ? + $this->mergeGroupedRoutes($this->$k, $v) : + $this->mergeGroupedRoutes($v, $this->$k); + } else { + $this->$k = $prepend === false ? + $this->mergeRoutes($this->$k, $v) : + $this->mergeRoutes($v, $this->$k); + } + } + } + + /** + * Merge two array, each containing routes grouped by method. + * + * @param array + * @param array + * @return array + */ + public function mergeGroupedRoutes(array $r1, array $r2) + { + $methods = array('GET', 'POST', 'HEAD', 'PATH', 'PUT', 'DELETE'); + foreach ($methods as $method) { + if (isset($r2[$method]) === false) { + continue; + } + if (isset($r1[$method]) === false) { + $r1[$method] = array(); + } + + $r1[$method] = $this->mergeRoutes($r1[$method], $r2[$method]); + } + + return $r1; + } + + /** + * Merge two arrays, each containing routes. + * + * @param array + * @param array + * @return array + */ + public function mergeRoutes(array $r1, array $r2) + { + foreach ($r2 as $uri => $route) { + $r1[$uri] = $route; + } + + return $r1; + } +} diff --git a/src/Illuminate/CachedRouting/Router.php b/src/Illuminate/CachedRouting/Router.php new file mode 100644 index 00000000..567a7d4b --- /dev/null +++ b/src/Illuminate/CachedRouting/Router.php @@ -0,0 +1,246 @@ +routes = new RouteCollection; + } + + /** + * Indicate that the routes that are defined in the given callback + * should be cached. + * + * @param string $filename + * @param Closure $callback + * @param int $cacheMinutes + * @return string + */ + public function cache($filename, Closure $callback, $cacheMinutes = 1440) + { + // If $cacheMinutes is 0 or lower, there is no need to cache anything. + if ($cacheMinutes <= 0) { + // Call closure to define routes that should be cached. + call_user_func($callback, $this); + + // No cache key. + return null; + } + + $cacher = $this->container['cache']; + $cacheKey = $this->getCacheKey($filename); + + // Check if the current route group is cached. + if (($cache = $cacher->get($cacheKey)) !== null) { + $this->routes->restoreRouteCache($cache); + } else { + // Back up current RouteCollection contents. + $this->routes->saveRouteCollection(); + + // Call closure to define routes that should be cached. + call_user_func($callback, $this); + + // Put routes in cache. + $cache = $this->routes->getCacheableRoutes(); + $cacher->put($cacheKey, $cache, $cacheMinutes); + + // And restore the routes that shouldn't be cached. + $this->routes->restoreRouteCollection(); + } + + return $cacheKey; + } + + /** + * Clear the cached data for the given routes file. + * + * @param string $filename + */ + public function clearCache($filename) + { + $cacher = $this->container['cache']; + + $cacher->forget($this->getCacheKey($filename)); + } + + /** + * Get the key under which the routes cache for the given file should be stored. + * + * @param string $filename + * @return string + */ + protected function getCacheKey($filename) + { + return 'routes.cache.'.$this->cacheVersion.'.'.md5($filename).filemtime($filename); + } + + /** + * Determine if the action is routing to a controller. + * + * @param array $action + * @return bool + */ + public function routingToController($action) + { + return parent::routingToController($action); + } + + /** + * Add a controller based route action to the action array. + * + * @param array|string $action + * @return array + */ + protected function getControllerAction($action) + { + if (is_string($action) === true) { + $action = array('uses' => $action); + } + + // Here we'll get an instance of this controller dispatcher and hand it off to + // the Closure so it will be used to resolve the class instances out of our + // IoC container instance and call the appropriate methods on the class. + if (count($this->groupStack) > 0) { + $action['uses'] = $this->prependGroupUses($action['uses']); + } + + // Here we'll get an instance of this controller dispatcher and hand it off to + // the Closure so it will be used to resolve the class instances out of our + // IoC container instance and call the appropriate methods on the class. + $action['controller'] = $action['uses']; + + $closure = $action['uses']; + + return array_set($action, 'uses', $closure); + } + + /** + * Replace the string action in the given array with a Closure to call. + * + * @param array $action + * @return array + */ + public function makeControllerActionClosure(array $action) + { + $closure = $this->getClassClosure($action['uses']); + + return array_set($action, 'uses', $closure); + } + + /** + * Create a new route instance. + * + * @param array|string $methods + * @param string $uri + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + protected function createRoute($methods, $uri, $action) + { + // If the route is routing to a controller we will parse the route action into + // an acceptable array format before registering it and creating this route + // instance itself. We need to build the Closure that will call this out. + if ($this->routingToController($action) === true) { + $action = $this->getControllerAction($action); + } + + $route = $this->newRoute( + $methods, + $uri = $this->prefix($uri), + $action + ); + + // If we have groups that need to be merged, we will merge them now after this + // route has already been created and is ready to go. After we're done with + // the merge we will be ready to return the route back out to the caller. + if (empty($this->groupStack) === false) { + $this->mergeController($route); + } + + $this->addWhereClausesToRoute($route); + + return $route; + } + + /** + * Create a new Route object. + * + * @param array|string $methods + * @param string $uri + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + protected function newRoute($methods, $uri, $action) + { + return new Route($methods, $uri, $action); + } + + /** + * Add the necessary where clauses to the route based on its initial registration. + * + * @param \Illuminate\Routing\Route $route + * @return \Illuminate\Routing\Route + */ + protected function addWhereClausesToRoute($route) + { + $route->where( + array_merge($this->patterns, array_get($route->getAction(), 'where', array())) + ); + return $route; + } +} diff --git a/src/Illuminate/CachedRouting/RoutingServiceProvider.php b/src/Illuminate/CachedRouting/RoutingServiceProvider.php new file mode 100644 index 00000000..eff880aa --- /dev/null +++ b/src/Illuminate/CachedRouting/RoutingServiceProvider.php @@ -0,0 +1,74 @@ +registerRouter(); + } + + /** + * Register the router instance. + * + * @return void + */ + protected function registerRouter() + { + $this->app['router'] = $this->app->share( + function ($app) { + $router = new Router($app['events'], $app); + + // If the current application environment is "testing", we will disable the + // routing filters, since they can be tested independently of the routes + // and just get in the way of our typical controller testing concerns. + if ($app['env'] === 'testing') { + $router->disableFilters(); + } + + return $router; + } + ); + } +} diff --git a/tests/CachedRouting/IntegrationTest.php b/tests/CachedRouting/IntegrationTest.php new file mode 100755 index 00000000..6a84b5d4 --- /dev/null +++ b/tests/CachedRouting/IntegrationTest.php @@ -0,0 +1,403 @@ +app === null) { + $this->refreshApplication(); + } + } + + /** + * Refresh the application instance. + * + * @return void + */ + protected function refreshApplication() + { + $this->app = $this->createApplication(); + + Facade::setFacadeApplication($this->app); + + $this->app['env'] = 'testing'; + + $this->app['path.storage'] = __DIR__; + + $loader = $this->getMockBuilder('Illuminate\Config\LoaderInterface') + ->setMethods(array('load', 'exists', 'getNamespaces', 'cascadePackage')) + ->getMockForAbstractClass(); + + $loader->method('load') + ->will($this->returnValue(array())); + + $loader->method('exists') + ->will($this->returnValue(true)); + + $loader->method('getNamespaces') + ->will($this->returnValue(array())); + + $loader->method('cascadePackage') + ->will($this->returnValue(array())); + + $this->app['config'] = new Repository($loader, $this->app['env']); + + $this->app['cache'] = new CacheManager($this->app); + $this->app['config']['cache.driver'] = 'array'; + + $this->app['session'] = new SessionManager($this->app); + $this->app['config']['session.driver'] = 'array'; + $this->app['session.store'] = $this->app['session']->driver(); + + $this->app->boot(); + } + + /** + * Creates the application. + * + * @return \Symfony\Component\HttpKernel\HttpKernelInterface + */ + protected function createApplication() + { + return new Application; + } + + /** + * Create a router. + * + * @return Router + */ + protected function getRouter() + { + return new Router($this->app['events'], $this->app); + } + + /** + * Create a new HttpKernel client instance. + * + * @param array $server + * @return \Symfony\Component\HttpKernel\Client + */ + protected function createClient(array $server = array()) + { + return new Client($this->app, $server); + } + + public function testCacheRoutes() + { + $router = $this->getRouter(); + + $key = $router->cache(__FILE__, function () use ($router) { + $router->get('/', 'HomeController@actionIndex'); + }); + + $this->assertTrue($this->app->cache->has($key), 'Routes must be in cache'); + $this->assertEquals(1, $router->getRoutes()->count(), 'Routes must be in collection'); + + $cachedRoutes = unserialize($this->app->cache->get($key)); + $this->assertArrayHasKey('routes', $cachedRoutes); + $this->assertArrayHasKey('GET', $cachedRoutes['routes']); + $this->assertCount(1, $cachedRoutes['routes']['GET']); + + // Next request should not call the callback. + $router = $this->getRouter(); + $router->cache(__FILE__, function () use ($router) { + throw new Exception('This should not be called'); + }); + $this->assertEquals(1, $router->getRoutes()->count(), 'Routes must be obtained from cache'); + } + + public function testCacheRoutesNoTtl() + { + $router = $this->getRouter(); + + $key = $router->cache(__FILE__, function () use ($router) { + $router->get('/', 'HomeController@actionIndex'); + }, 0); + + $this->assertNull($key, 'Cache key should be null with TTL=0'); + $this->assertFalse($this->app->cache->has($key), 'Key should not be stored in cache'); + $this->assertEquals(1, $router->getRoutes()->count(), 'Route must be added to router'); + } + + public function testAllMethodsWorks() + { + $methods = array('get', 'post', 'put', 'patch', 'delete'); + + $router = $this->getRouter(); + + $key = $router->cache(__FILE__, function () use ($router, $methods) { + foreach ($methods as $method) { + $router->$method('/', 'HomeController@action' . ucfirst($method)); + } + }); + + $this->assertTrue($this->app->cache->has($key), 'Routes must be in cache'); + $this->assertEquals(count($methods), $router->getRoutes()->count(), 'Routes must be in collection'); + + $cachedRoutes = unserialize($this->app->cache->get($key)); + $this->assertArrayHasKey('routes', $cachedRoutes); + + foreach ($methods as $method) { + $this->assertArrayHasKey(strtoupper($method), $cachedRoutes['routes']); + $this->assertCount(1, $cachedRoutes['routes'][strtoupper($method)]); + } + + // Next request should not call the callback. + $router = $this->getRouter(); + $router->cache(__FILE__, function () use ($router) { + throw new Exception('This should not be called'); + }); + $this->assertEquals(count($methods), $router->getRoutes()->count(), 'Routes must be obtained from cache'); + } + + public function testControllerRouting() + { + $router = $this->getRouter(); + + $controllerName = str_shuffle('abcdefghijklmnopqrstuvwxyz'); + + // Create a controller class. + eval('class ' . $controllerName . ' extends Illuminate\Routing\Controller { public function getHomePage() {} }'); + + $key = $router->cache(__FILE__, function () use ($router, $controllerName) { + $router->controller('/', $controllerName); + }); + + $this->assertTrue($this->app->cache->has($key), 'Routes must be in cache'); + // 2 because controller adds missingMethod + $this->assertEquals(2, $router->getRoutes()->count(), 'Routes must be in collection'); + + // Next request should not call the callback. + $router = $this->getRouter(); + $router->cache(__FILE__, function () use ($router) { + throw new Exception('This should not be called'); + }); + $this->assertEquals(2, $router->getRoutes()->count(), 'Routes must be obtained from cache'); + } + + public function testCanDispatchRequest() + { + // Create a controller class. + $controllerName = str_shuffle('abcdefghijklmnopqrstuvwxyz'); + eval('class ' . $controllerName . ' extends Illuminate\Routing\Controller { + public function getIndex() { + return Illuminate\Support\Facades\Response::make(1); + } + }'); + + // First, define a route. + $router = $this->getRouter(); + $router->cache(__FILE__, function () use ($router, $controllerName) { + $router->get('/', $controllerName . '@getIndex'); + }); + + // Create a new router, set it on the app, and simulate a request. + $this->app['router'] = $this->getRouter(); + $this->app['router']->cache(__FILE__, function () use ($router) { + throw new Exception('This should not be called'); + }); + + $client = $this->createClient(); + $client->request('get', '/'); + + $response = $client->getResponse(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + $this->assertEquals(1, $response->getContent()); + } + + public function testCanRouteToClosure() + { + // Create a new router, set it on the app, and simulate a request. + $this->app['router'] = $this->getRouter(); + $this->app['router']->get('/', function () { + return 1; + }); + + $client = $this->createClient(); + $client->request('get', '/'); + + $response = $client->getResponse(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + $this->assertEquals(1, $response->getContent()); + } + + public function testCanGroupRoutes() + { + $router = $this->getRouter(); + + $controllerName = str_shuffle('abcdefghijklmnopqrstuvwxyz'); + + // Create a controller class. + eval('class ' . $controllerName . ' extends Illuminate\Routing\Controller { + public function getHomePage() { + return Illuminate\Support\Facades\Response::make(1); + } + }'); + + $router->cache(__FILE__, function () use ($controllerName, $router) { + $router->group( + array('prefix' => 'grouped'), + function () use ($router, $controllerName) { + $router->get('/', $controllerName . '@getHomePage'); + $router->get('/dashboard', $controllerName . '@getHomePage'); + } + ); + }); + + // 2 routes originating from group closure + $this->assertEquals(2, $router->getRoutes()->count(), 'Routes must be in collection'); + + // Create a new router, set it on the app, and simulate a request. + $this->app['router'] = $this->getRouter(); + $this->app['router']->cache(__FILE__, function () use ($router) { + throw new Exception('This should not be called'); + }); + + $client = $this->createClient(); + $client->request('GET', '/grouped/dashboard'); + + $response = $client->getResponse(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + $this->assertEquals(1, $response->getContent()); + } + + /** + * @expectedException Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testCanChainWheres() + { + $this->app['router'] = $this->getRouter(); + $this->app['router']->get('/{foo}/{bar}', function () { + return 'baz'; + })->where('foo', '\w+')->where('bar', '\d+'); + + // /herp/derp should not match above route. + $client = $this->createClient(); + $client->request('GET', '/herp/derp'); + } + + /** + * @expectedException Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testWheresRetainedInCache() + { + // Create a controller class. + $controllerName = str_shuffle('abcdefghijklmnopqrstuvwxyz'); + eval('class ' . $controllerName . ' extends Illuminate\Routing\Controller { + public function getIndex() { + return Illuminate\Support\Facades\Response::make(1); + } + }'); + + // First, define a route. + $router = $this->getRouter(); + $router->cache(__FILE__, function () use ($router, $controllerName) { + $router->get('/{foo}/{bar}', $controllerName . '@getIndex')->where('foo', '\w+')->where('bar', '\d+'); + }); + + // Create a new router, set it on the app, and simulate a request. + $this->app['router'] = $this->getRouter(); + $this->app['router']->cache(__FILE__, function () use ($router) { + throw new Exception('This should not be called'); + }); + + // /herp/derp should not match above route. + $client = $this->createClient(); + $client->request('GET', '/herp/derp'); + } + + public function testCanUseResource() + { + // Create a controller class. + $controllerName = str_shuffle('abcdefghijklmnopqrstuvwxyz'); + eval('class ' . $controllerName . ' extends Illuminate\Routing\Controller { }'); + + // First, define a resource. + $router = $this->getRouter(); + $key = $router->cache(__FILE__, function () use ($router, $controllerName) { + $router->resource('item', $controllerName); + }); + + $this->assertTrue($this->app->cache->has($key), 'Routes must be in cache'); + $this->assertEquals(8, $router->getRoutes()->count(), 'Routes must be in collection'); + + // Next request should not call the callback. + $router = $this->getRouter(); + $router->cache(__FILE__, function () use ($router) { + throw new Exception('This should not be called'); + }); + $this->assertEquals(8, $router->getRoutes()->count(), 'Routes must be obtained from cache'); + } + + public function testCanClearCache() + { + // Create a controller class. + $controllerName = str_shuffle('abcdefghijklmnopqrstuvwxyz'); + eval('class ' . $controllerName . ' extends Illuminate\Routing\Controller { }'); + + // First, define a route. + $router = $this->getRouter(); + $key = $router->cache(__FILE__, function () use ($router, $controllerName) { + $router->get('/{foo}/{bar}', $controllerName . '@getIndex')->where('foo', '\w+')->where('bar', '\d+'); + }); + $this->assertTrue($this->app->cache->has($key), 'Routes must be in cache'); + + // Next, clear it. + $router->clearCache(__FILE__); + $this->assertFalse($this->app->cache->has($key), 'Routes must no longer be cached'); + } +} diff --git a/tests/CachedRouting/RoutingServiceProviderTest.php b/tests/CachedRouting/RoutingServiceProviderTest.php new file mode 100644 index 00000000..22374ab8 --- /dev/null +++ b/tests/CachedRouting/RoutingServiceProviderTest.php @@ -0,0 +1,98 @@ +app === null) { + $this->refreshApplication(); + } + } + + /** + * Refresh the application instance. + * + * @return void + */ + protected function refreshApplication() + { + $this->app = $this->createApplication(); + + $this->app['env'] = 'testing'; + + $this->app->boot(); + } + + /** + * Creates the application. + * + * @return \Symfony\Component\HttpKernel\HttpKernelInterface + */ + public function createApplication() + { + return new Application; + } + + /** + * @covers ::register + * @covers ::registerRouter + */ + public function testRegister() + { + $provider = new RoutingServiceProvider($this->app); + $provider->register(); + + $this->assertInstanceOf('MaartenStaa\Routing\Router', $this->app->router); + } +} From de7d13d2774c2c564106bbc48e4dbc01cc43b940 Mon Sep 17 00:00:00 2001 From: agis Date: Wed, 8 Jul 2026 14:53:57 +0700 Subject: [PATCH 3/4] migrate syntax --- src/Illuminate/CachedRouting/Route.php | 2 ++ src/Illuminate/CachedRouting/Router.php | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/CachedRouting/Route.php b/src/Illuminate/CachedRouting/Route.php index 385466d5..c0870cb4 100644 --- a/src/Illuminate/CachedRouting/Route.php +++ b/src/Illuminate/CachedRouting/Route.php @@ -51,6 +51,7 @@ class Route extends LaravelRoute * * @return mixed */ + #[\Override] public function run() { $app = app(); @@ -81,6 +82,7 @@ protected function shouldCompile() /** * Compile the route into a Symfony CompiledRoute instance. */ + #[\Override] protected function compileRoute() { if ($this->shouldCompile() === true && $this->compiled === null) { diff --git a/src/Illuminate/CachedRouting/Router.php b/src/Illuminate/CachedRouting/Router.php index 567a7d4b..0a455603 100644 --- a/src/Illuminate/CachedRouting/Router.php +++ b/src/Illuminate/CachedRouting/Router.php @@ -55,7 +55,7 @@ class Router extends LaravelRouter * @param \Illuminate\Events\Dispatcher $events * @param \Illuminate\Container\Container|null $container */ - public function __construct(Dispatcher $events, Container $container = null) + public function __construct(Dispatcher $events, ?Container $container = null) { parent::__construct($events, $container); @@ -135,6 +135,7 @@ protected function getCacheKey($filename) * @param array $action * @return bool */ + #[\Override] public function routingToController($action) { return parent::routingToController($action); @@ -146,6 +147,7 @@ public function routingToController($action) * @param array|string $action * @return array */ + #[\Override] protected function getControllerAction($action) { if (is_string($action) === true) { @@ -190,6 +192,7 @@ public function makeControllerActionClosure(array $action) * @param mixed $action * @return \Illuminate\Routing\Route */ + #[\Override] protected function createRoute($methods, $uri, $action) { // If the route is routing to a controller we will parse the route action into @@ -225,6 +228,7 @@ protected function createRoute($methods, $uri, $action) * @param mixed $action * @return \Illuminate\Routing\Route */ + #[\Override] protected function newRoute($methods, $uri, $action) { return new Route($methods, $uri, $action); @@ -236,6 +240,7 @@ protected function newRoute($methods, $uri, $action) * @param \Illuminate\Routing\Route $route * @return \Illuminate\Routing\Route */ + #[\Override] protected function addWhereClausesToRoute($route) { $route->where( From a321e42250cecdf7e033eb5fe7c52d1200a39cc1 Mon Sep 17 00:00:00 2001 From: agis Date: Wed, 8 Jul 2026 15:51:05 +0700 Subject: [PATCH 4/4] pass tests --- ...ionTest.php => RoutingIntegrationTest.php} | 181 ++++++++---------- .../RoutingServiceProviderTest.php | 27 ++- 2 files changed, 93 insertions(+), 115 deletions(-) rename tests/CachedRouting/{IntegrationTest.php => RoutingIntegrationTest.php} (63%) diff --git a/tests/CachedRouting/IntegrationTest.php b/tests/CachedRouting/RoutingIntegrationTest.php similarity index 63% rename from tests/CachedRouting/IntegrationTest.php rename to tests/CachedRouting/RoutingIntegrationTest.php index 6a84b5d4..a826358c 100755 --- a/tests/CachedRouting/IntegrationTest.php +++ b/tests/CachedRouting/RoutingIntegrationTest.php @@ -1,4 +1,4 @@ -app === null) { $this->refreshApplication(); @@ -64,10 +63,8 @@ public function setUp() /** * Refresh the application instance. - * - * @return void */ - protected function refreshApplication() + protected function refreshApplication(): void { $this->app = $this->createApplication(); @@ -77,21 +74,12 @@ protected function refreshApplication() $this->app['path.storage'] = __DIR__; - $loader = $this->getMockBuilder('Illuminate\Config\LoaderInterface') - ->setMethods(array('load', 'exists', 'getNamespaces', 'cascadePackage')) - ->getMockForAbstractClass(); + $loader = $this->createMock('Illuminate\Config\LoaderInterface'); - $loader->method('load') - ->will($this->returnValue(array())); - - $loader->method('exists') - ->will($this->returnValue(true)); - - $loader->method('getNamespaces') - ->will($this->returnValue(array())); - - $loader->method('cascadePackage') - ->will($this->returnValue(array())); + $loader->method('load')->willReturn([]); + $loader->method('exists')->willReturn(true); + $loader->method('getNamespaces')->willReturn([]); + $loader->method('cascadePackage')->willReturn([]); $this->app['config'] = new Repository($loader, $this->app['env']); @@ -107,36 +95,29 @@ protected function refreshApplication() /** * Creates the application. - * - * @return \Symfony\Component\HttpKernel\HttpKernelInterface */ - protected function createApplication() + protected function createApplication(): Application { - return new Application; + return new Application(); } /** * Create a router. - * - * @return Router */ - protected function getRouter() + protected function getRouter(): Router { return new Router($this->app['events'], $this->app); } /** * Create a new HttpKernel client instance. - * - * @param array $server - * @return \Symfony\Component\HttpKernel\Client */ - protected function createClient(array $server = array()) + protected function createClient(array $server = array()): HttpKernelBrowser { - return new Client($this->app, $server); + return new HttpKernelBrowser($this->app, $server); } - public function testCacheRoutes() + public function testCacheRoutes(): void { $router = $this->getRouter(); @@ -144,23 +125,23 @@ public function testCacheRoutes() $router->get('/', 'HomeController@actionIndex'); }); - $this->assertTrue($this->app->cache->has($key), 'Routes must be in cache'); - $this->assertEquals(1, $router->getRoutes()->count(), 'Routes must be in collection'); + static::assertTrue($this->app->cache->has($key), 'Routes must be in cache'); + static::assertEquals(1, $router->getRoutes()->count(), 'Routes must be in collection'); $cachedRoutes = unserialize($this->app->cache->get($key)); - $this->assertArrayHasKey('routes', $cachedRoutes); - $this->assertArrayHasKey('GET', $cachedRoutes['routes']); - $this->assertCount(1, $cachedRoutes['routes']['GET']); + static::assertArrayHasKey('routes', $cachedRoutes); + static::assertArrayHasKey('GET', $cachedRoutes['routes']); + static::assertCount(1, $cachedRoutes['routes']['GET']); // Next request should not call the callback. $router = $this->getRouter(); $router->cache(__FILE__, function () use ($router) { - throw new Exception('This should not be called'); + throw new RuntimeException('This should not be called'); }); - $this->assertEquals(1, $router->getRoutes()->count(), 'Routes must be obtained from cache'); + static::assertEquals(1, $router->getRoutes()->count(), 'Routes must be obtained from cache'); } - public function testCacheRoutesNoTtl() + public function testCacheRoutesNoTtl(): void { $router = $this->getRouter(); @@ -168,14 +149,14 @@ public function testCacheRoutesNoTtl() $router->get('/', 'HomeController@actionIndex'); }, 0); - $this->assertNull($key, 'Cache key should be null with TTL=0'); - $this->assertFalse($this->app->cache->has($key), 'Key should not be stored in cache'); - $this->assertEquals(1, $router->getRoutes()->count(), 'Route must be added to router'); + static::assertNull($key, 'Cache key should be null with TTL=0'); + static::assertFalse($this->app->cache->has($key), 'Key should not be stored in cache'); + static::assertEquals(1, $router->getRoutes()->count(), 'Route must be added to router'); } - public function testAllMethodsWorks() + public function testAllMethodsWorks(): void { - $methods = array('get', 'post', 'put', 'patch', 'delete'); + $methods = ['get', 'post', 'put', 'patch', 'delete']; $router = $this->getRouter(); @@ -185,26 +166,26 @@ public function testAllMethodsWorks() } }); - $this->assertTrue($this->app->cache->has($key), 'Routes must be in cache'); - $this->assertEquals(count($methods), $router->getRoutes()->count(), 'Routes must be in collection'); + static::assertTrue($this->app->cache->has($key), 'Routes must be in cache'); + static::assertEquals(count($methods), $router->getRoutes()->count(), 'Routes must be in collection'); $cachedRoutes = unserialize($this->app->cache->get($key)); - $this->assertArrayHasKey('routes', $cachedRoutes); + static::assertArrayHasKey('routes', $cachedRoutes); foreach ($methods as $method) { - $this->assertArrayHasKey(strtoupper($method), $cachedRoutes['routes']); - $this->assertCount(1, $cachedRoutes['routes'][strtoupper($method)]); + static::assertArrayHasKey(strtoupper($method), $cachedRoutes['routes']); + static::assertCount(1, $cachedRoutes['routes'][strtoupper($method)]); } // Next request should not call the callback. $router = $this->getRouter(); $router->cache(__FILE__, function () use ($router) { - throw new Exception('This should not be called'); + throw new RuntimeException('This should not be called'); }); - $this->assertEquals(count($methods), $router->getRoutes()->count(), 'Routes must be obtained from cache'); + static::assertEquals(count($methods), $router->getRoutes()->count(), 'Routes must be obtained from cache'); } - public function testControllerRouting() + public function testControllerRouting(): void { $router = $this->getRouter(); @@ -217,19 +198,19 @@ public function testControllerRouting() $router->controller('/', $controllerName); }); - $this->assertTrue($this->app->cache->has($key), 'Routes must be in cache'); + static::assertTrue($this->app->cache->has($key), 'Routes must be in cache'); // 2 because controller adds missingMethod - $this->assertEquals(2, $router->getRoutes()->count(), 'Routes must be in collection'); + static::assertEquals(2, $router->getRoutes()->count(), 'Routes must be in collection'); // Next request should not call the callback. $router = $this->getRouter(); $router->cache(__FILE__, function () use ($router) { throw new Exception('This should not be called'); }); - $this->assertEquals(2, $router->getRoutes()->count(), 'Routes must be obtained from cache'); + static::assertEquals(2, $router->getRoutes()->count(), 'Routes must be obtained from cache'); } - public function testCanDispatchRequest() + public function testCanDispatchRequest(): void { // Create a controller class. $controllerName = str_shuffle('abcdefghijklmnopqrstuvwxyz'); @@ -248,34 +229,32 @@ public function getIndex() { // Create a new router, set it on the app, and simulate a request. $this->app['router'] = $this->getRouter(); $this->app['router']->cache(__FILE__, function () use ($router) { - throw new Exception('This should not be called'); + throw new RuntimeException('This should not be called'); }); $client = $this->createClient(); $client->request('get', '/'); $response = $client->getResponse(); - $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); - $this->assertEquals(1, $response->getContent()); + static::assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + static::assertEquals(1, $response->getContent()); } - public function testCanRouteToClosure() + public function testCanRouteToClosure(): void { // Create a new router, set it on the app, and simulate a request. $this->app['router'] = $this->getRouter(); - $this->app['router']->get('/', function () { - return 1; - }); + $this->app['router']->get('/', fn() => 1); $client = $this->createClient(); $client->request('get', '/'); $response = $client->getResponse(); - $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); - $this->assertEquals(1, $response->getContent()); + static::assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + static::assertEquals(1, $response->getContent()); } - public function testCanGroupRoutes() + public function testCanGroupRoutes(): void { $router = $this->getRouter(); @@ -290,7 +269,7 @@ public function getHomePage() { $router->cache(__FILE__, function () use ($controllerName, $router) { $router->group( - array('prefix' => 'grouped'), + ['prefix' => 'grouped'], function () use ($router, $controllerName) { $router->get('/', $controllerName . '@getHomePage'); $router->get('/dashboard', $controllerName . '@getHomePage'); @@ -299,42 +278,41 @@ function () use ($router, $controllerName) { }); // 2 routes originating from group closure - $this->assertEquals(2, $router->getRoutes()->count(), 'Routes must be in collection'); + static::assertEquals(2, $router->getRoutes()->count(), 'Routes must be in collection'); // Create a new router, set it on the app, and simulate a request. $this->app['router'] = $this->getRouter(); - $this->app['router']->cache(__FILE__, function () use ($router) { - throw new Exception('This should not be called'); + $this->app['router']->cache(__FILE__, callback: function () use ($router) { + throw new RuntimeException('This should not be called'); }); $client = $this->createClient(); $client->request('GET', '/grouped/dashboard'); $response = $client->getResponse(); - $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); - $this->assertEquals(1, $response->getContent()); + static::assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + static::assertEquals(1, $response->getContent()); } - /** - * @expectedException Symfony\Component\HttpKernel\Exception\NotFoundHttpException - */ - public function testCanChainWheres() + public function testCanChainWheres(): void { + $this->expectException(NotFoundHttpException::class); + $this->app['router'] = $this->getRouter(); - $this->app['router']->get('/{foo}/{bar}', function () { - return 'baz'; - })->where('foo', '\w+')->where('bar', '\d+'); + $this->app['router']->get('/{foo}/{bar}', fn() => 'baz') + ->where('foo', '\w+') + ->where('bar', '\d+'); // /herp/derp should not match above route. $client = $this->createClient(); + $client->catchExceptions(false); $client->request('GET', '/herp/derp'); } - /** - * @expectedException Symfony\Component\HttpKernel\Exception\NotFoundHttpException - */ - public function testWheresRetainedInCache() + public function testWheresRetainedInCache(): void { + $this->expectException(NotFoundHttpException::class); + // Create a controller class. $controllerName = str_shuffle('abcdefghijklmnopqrstuvwxyz'); eval('class ' . $controllerName . ' extends Illuminate\Routing\Controller { @@ -351,16 +329,17 @@ public function getIndex() { // Create a new router, set it on the app, and simulate a request. $this->app['router'] = $this->getRouter(); - $this->app['router']->cache(__FILE__, function () use ($router) { - throw new Exception('This should not be called'); + $this->app['router']->cache(__FILE__, callback: function () use ($router) { + throw new RuntimeException('This should not be called'); }); // /herp/derp should not match above route. $client = $this->createClient(); + $client->catchExceptions(false); $client->request('GET', '/herp/derp'); } - public function testCanUseResource() + public function testCanUseResource(): void { // Create a controller class. $controllerName = str_shuffle('abcdefghijklmnopqrstuvwxyz'); @@ -372,18 +351,18 @@ public function testCanUseResource() $router->resource('item', $controllerName); }); - $this->assertTrue($this->app->cache->has($key), 'Routes must be in cache'); - $this->assertEquals(8, $router->getRoutes()->count(), 'Routes must be in collection'); + static::assertTrue($this->app->cache->has($key), 'Routes must be in cache'); + static::assertEquals(8, $router->getRoutes()->count(), 'Routes must be in collection'); // Next request should not call the callback. $router = $this->getRouter(); $router->cache(__FILE__, function () use ($router) { - throw new Exception('This should not be called'); + throw new RuntimeException('This should not be called'); }); - $this->assertEquals(8, $router->getRoutes()->count(), 'Routes must be obtained from cache'); + static::assertEquals(8, $router->getRoutes()->count(), 'Routes must be obtained from cache'); } - public function testCanClearCache() + public function testCanClearCache(): void { // Create a controller class. $controllerName = str_shuffle('abcdefghijklmnopqrstuvwxyz'); @@ -394,10 +373,10 @@ public function testCanClearCache() $key = $router->cache(__FILE__, function () use ($router, $controllerName) { $router->get('/{foo}/{bar}', $controllerName . '@getIndex')->where('foo', '\w+')->where('bar', '\d+'); }); - $this->assertTrue($this->app->cache->has($key), 'Routes must be in cache'); + static::assertTrue($this->app->cache->has($key), 'Routes must be in cache'); // Next, clear it. $router->clearCache(__FILE__); - $this->assertFalse($this->app->cache->has($key), 'Routes must no longer be cached'); + static::assertFalse($this->app->cache->has($key), 'Routes must no longer be cached'); } } diff --git a/tests/CachedRouting/RoutingServiceProviderTest.php b/tests/CachedRouting/RoutingServiceProviderTest.php index 22374ab8..05588ca4 100644 --- a/tests/CachedRouting/RoutingServiceProviderTest.php +++ b/tests/CachedRouting/RoutingServiceProviderTest.php @@ -1,4 +1,4 @@ -app === null) { $this->refreshApplication(); @@ -65,7 +66,7 @@ public function setUp() * * @return void */ - protected function refreshApplication() + protected function refreshApplication(): void { $this->app = $this->createApplication(); @@ -76,23 +77,21 @@ protected function refreshApplication() /** * Creates the application. - * - * @return \Symfony\Component\HttpKernel\HttpKernelInterface */ - public function createApplication() + public function createApplication(): Application { - return new Application; + return new Application(); } /** * @covers ::register * @covers ::registerRouter */ - public function testRegister() + public function testRegister(): void { $provider = new RoutingServiceProvider($this->app); $provider->register(); - $this->assertInstanceOf('MaartenStaa\Routing\Router', $this->app->router); + static::assertInstanceOf(Router::class, $this->app->router); } }