From e9175e728745cba3d88cb175560142361f339cc0 Mon Sep 17 00:00:00 2001 From: Mehmet Korkmaz Date: Sat, 17 Jun 2017 08:21:54 +0300 Subject: [PATCH] Caching routes enabled --- src/Router.php | 84 +++++++++++++++++++++++++++++++++++++-------- test/RouterTest.php | 36 +++++++++++++++---- 2 files changed, 100 insertions(+), 20 deletions(-) diff --git a/src/Router.php b/src/Router.php index 5e228c4..5ae7abc 100644 --- a/src/Router.php +++ b/src/Router.php @@ -54,14 +54,25 @@ final class Router /** * Default return type if not noted in the $routes + * * @var string */ private $defaultReturnType; + /** + * @var null|string + */ + private $cachedFile; + + /** + * @var array + */ + private $routerClosures = []; /** * Translation array. * Make sures about return type. + * * @var array */ private static $translations = [ @@ -115,21 +126,24 @@ final class Router * @param string $method * @param string $requestedPath * @param string $folder + * @param string $cachedFile * @throws UnexpectedValueException */ public function __construct( string $defaultReturnType, string $method, string $requestedPath, - string $folder = '' + string $folder = '', + ?string $cachedFile = null ) { if (!in_array($method, self::$validRequestMethods, true)) { $message = sprintf('%s is not valid Http request method.', $method); throw new UnexpectedValueException($message); } - $this->method = $method; - $this->requestedPath = $this->extractFolder($requestedPath, $folder); + $this->method = $method; + $this->requestedPath = $this->extractFolder($requestedPath, $folder); $this->defaultReturnType = self::$translations[$defaultReturnType] ?? self::$validReturnTypes[0]; + $this->cachedFile = $cachedFile; } /** @@ -236,14 +250,22 @@ private function checkRequestMethodParameterType($requestMethod) : void /** * Dispatch against the provided HTTP method verb and URI. - * @return array + * @return FastRoute\Dispatcher */ - private function dispatcher() + private function dispatcher() : FastRoute\Dispatcher + { + if ($this->cachedFile !== null) { + return $this->cachedDispatcher(); + } + return $this->simpleDispatcher(); + } + + private function simpleDispatcher() { $options = [ - 'routeParser' => FastRoute\RouteParser\Std::class, + 'routeParser' => FastRoute\RouteParser\Std::class, 'dataGenerator' => FastRoute\DataGenerator\GroupCountBased::class, - 'dispatcher' => FastRoute\Dispatcher\GroupCountBased::class, + 'dispatcher' => FastRoute\Dispatcher\GroupCountBased::class, 'routeCollector' => FastRoute\RouteCollector::class, ]; /** @var RouteCollector $routeCollector */ @@ -251,22 +273,55 @@ private function dispatcher() new $options['routeParser'], new $options['dataGenerator'] ); $this->addRoutes($routeCollector); + return new $options['dispatcher']($routeCollector->getData()); } + private function cachedDispatcher() + { + $options = [ + 'routeParser' => FastRoute\RouteParser\Std::class, + 'dataGenerator' => FastRoute\DataGenerator\GroupCountBased::class, + 'dispatcher' => FastRoute\Dispatcher\GroupCountBased::class, + 'routeCollector' => FastRoute\RouteCollector::class + ]; + if (file_exists($this->cachedFile)) { + $dispatchData = require $this->cachedFile; + if (!is_array($dispatchData)) { + throw new \RuntimeException('Invalid cache file "' . $options['cacheFile'] . '"'); + } + return new $options['dispatcher']($dispatchData); + } + $routeCollector = new $options['routeCollector']( + new $options['routeParser'], new $options['dataGenerator'] + ); + $this->addRoutes($routeCollector); + /** @var RouteCollector $routeCollector */ + $dispatchData = $routeCollector->getData(); + file_put_contents( + $this->cachedFile, + 'routes as $definedRoute) { $definedRoute[3] = $definedRoute[3] ?? $this->defaultReturnType; - $route->addRoute(strtoupper($definedRoute[0]), $definedRoute[1], function ($args) use ($definedRoute) { - [$null1, $null2, $controller, $returnType] = $definedRoute; - $returnType = Router::$translations[$returnType] ?? $this->defaultReturnType; + $routeName = 'routeClosure'.$routeIndex; + [$null1, $null2, $controller, $returnType] = $definedRoute; + $returnType = Router::$translations[$returnType] ?? $this->defaultReturnType; + $this->routerClosures[$routeName] = function($args) use ($controller, $returnType) { return ['controller' => $controller, 'returnType'=> $returnType, 'args'=> $args]; - }); + }; + $route->addRoute(strtoupper($definedRoute[0]), $definedRoute[1], $routeName); + $routeIndex++; } } @@ -280,8 +335,9 @@ public function getRoute() : array { $dispatcher = $this->dispatcher(); $routeInfo = $dispatcher->dispatch($this->method, $this->requestedPath); + $route = $this->runDispatcher($routeInfo); $routerData = [ - 'route' => $this->runDispatcher($routeInfo), + 'route' => $route, 'aliases' => $this->aliases ]; return $routerData; @@ -319,7 +375,7 @@ private function getRouteData(array $routeInfo) : array { if ($routeInfo[0] === FastRoute\Dispatcher::FOUND) { [$null1, $handler, $vars] = $routeInfo; - return $handler($vars); + return $this->routerClosures[$handler]($vars); } return [ 'status' => 200, diff --git a/test/RouterTest.php b/test/RouterTest.php index 2973eb8..cac9097 100644 --- a/test/RouterTest.php +++ b/test/RouterTest.php @@ -18,11 +18,12 @@ class MyRouterClass extends TestCase private $request; - public function setUp() + public function setUp() : void { $basedir = dirname(__DIR__) . '/app'; $this->config['base_dir'] = $basedir; $this->config['app_dir'] = $basedir; + $this->config['cache_file'] = '/tmp/fastroute.cache'; $_SERVER = []; $_FILES = []; $_GET = []; @@ -50,7 +51,7 @@ public function setUp() * @param $folder string * @param $expected string */ - public function shouldExtractRouteFromURLSuccessfully($requestedPath, $folder, $expected) + public function shouldExtractRouteFromURLSuccessfully($requestedPath, $folder, $expected) : void { $router = new Selami\Router( $this->config['default_return_type'], @@ -73,7 +74,7 @@ public function shouldExtractRouteFromURLSuccessfully($requestedPath, $folder, $ ); } - public function extractFolderDataProvider() + public function extractFolderDataProvider() : array { return [ ['/', '', '/'], @@ -82,6 +83,25 @@ public function extractFolderDataProvider() ]; } + /** + * @test + */ + public function shouldCacheRoutesSuccessfully() : void + { + $router = new Selami\Router( + $this->config['default_return_type'], + $this->request->getMethod(), + $this->request->getUri()->getPath(), + $this->config['folder'], + $this->config['cache_file'] + ); + $router->add('get', '/', 'app/main', 'html', 'home'); + $router->getRoute(); + $this->assertFileExists($this->config['cache_file'], + 'Couldn\'t cache the file' + ); + } + /** * @test */ @@ -198,7 +218,6 @@ public function shouldThrowInvalidArgumentExceptionForAddMethodIfREquestMEthotIs $router->add(200, '/', 'app/main', null, 'home'); } - /** * @test */ @@ -212,7 +231,6 @@ public function shouldCorrectlyReturnMethodNotAllowed() $this->request->getUri()->getPath(), $this->config['folder'] ); - $router->add('get', '/', 'app/main', null, 'home'); $router->add('get', '/json', 'app/json', 'json'); $router->add('post', '/json', 'app/redirect', 'redirect'); @@ -235,7 +253,6 @@ public function shouldCorrectlyReturnNotFound() $this->request->getUri()->getPath(), $this->config['folder'] ); - $router->add('get', '/', 'app/main', null, 'home'); $router->add('get', '/json', 'app/json', 'json'); $router->add('post', '/json', 'app/redirect', 'redirect'); @@ -243,4 +260,11 @@ public function shouldCorrectlyReturnNotFound() $routeInfo = $router->getRoute(); $this->assertEquals('404', $routeInfo['route']['status'], "Router didn't correctly returnNot FOund"); } + + public function tearDown() + { + if (file_exists($this->config['cache_file'])) { + unlink($this->config['cache_file']); + } + } }