diff --git a/src/DataGenerator.php b/src/DataGenerator.php index 831539b..946dcfc 100644 --- a/src/DataGenerator.php +++ b/src/DataGenerator.php @@ -12,7 +12,7 @@ * @phpstan-type DynamicRouteChunks list * @phpstan-type DynamicRoutes array * @phpstan-type RoutesForUriGeneration array - * @phpstan-type RouteData array{StaticRoutes, DynamicRoutes} + * @phpstan-type RouteData array{StaticRoutes, DynamicRoutes, RoutesForUriGeneration} */ interface DataGenerator { diff --git a/src/DataGenerator/RegexBasedAbstract.php b/src/DataGenerator/RegexBasedAbstract.php index ccf5643..7d0bdbc 100644 --- a/src/DataGenerator/RegexBasedAbstract.php +++ b/src/DataGenerator/RegexBasedAbstract.php @@ -7,8 +7,10 @@ use FastRoute\DataGenerator; use FastRoute\Route; use FastRoute\RouteParser; +use RuntimeException; use function array_chunk; +use function array_key_exists; use function array_map; use function assert; use function ceil; @@ -16,6 +18,7 @@ use function is_string; use function max; use function round; +use function usort; /** * @internal @@ -25,6 +28,7 @@ * @phpstan-import-type DynamicRoutes from DataGenerator * @phpstan-import-type RouteData from DataGenerator * @phpstan-import-type ExtraParameters from DataGenerator + * @phpstan-import-type RoutesForUriGeneration from DataGenerator * @phpstan-import-type ParsedRoute from RouteParser */ abstract class RegexBasedAbstract implements DataGenerator @@ -35,6 +39,9 @@ abstract class RegexBasedAbstract implements DataGenerator /** @var array> */ protected array $methodToRegexToRoutesMap = []; + /** @var RoutesForUriGeneration */ + private array $uriGenerationData = []; + abstract protected function getApproxChunkSize(): int; /** @@ -52,16 +59,40 @@ public function addRoute(string $httpMethod, array $routeData, mixed $handler, a } else { $this->addVariableRoute($httpMethod, $routeData, $handler, $extraParameters); } + + $this->registerNamedRoute($routeData, $extraParameters); + } + + /** + * @param ParsedRoute $routeData + * @param ExtraParameters $extraParameters + */ + private function registerNamedRoute(array $routeData, array $extraParameters): void + { + if (! array_key_exists('_name', $extraParameters)) { + return; + } + + $routeName = $extraParameters['_name']; + + if (! is_string($routeName) || $routeName === '') { + throw new RuntimeException('broken!'); + } + + $this->uriGenerationData[$routeName] ??= []; + $this->uriGenerationData[$routeName][] = $routeData; } /** @inheritDoc */ public function getData(): array { + $routesForUriGeneration = $this->routesForUriGeneration(); + if ($this->methodToRegexToRoutesMap === []) { - return [$this->staticRoutes, []]; + return [$this->staticRoutes, [], $routesForUriGeneration]; } - return [$this->staticRoutes, $this->generateVariableRouteData()]; + return [$this->staticRoutes, $this->generateVariableRouteData(), $routesForUriGeneration]; } /** @return DynamicRoutes */ @@ -132,4 +163,20 @@ private function addVariableRoute(string $httpMethod, array $routeData, mixed $h $this->methodToRegexToRoutesMap[$httpMethod][$regex] = $route; } + + /** @return RoutesForUriGeneration */ + private function routesForUriGeneration(): array + { + return array_map( + static function (array $routes): array { + usort( + $routes, + static fn (array $routeA, array $routeB) => count($routeB) <=> count($routeA), + ); + + return $routes; + }, + $this->uriGenerationData, + ); + } } diff --git a/src/FastRoute.php b/src/FastRoute.php index 553a5c4..ebcedfa 100644 --- a/src/FastRoute.php +++ b/src/FastRoute.php @@ -6,10 +6,15 @@ use Closure; use FastRoute\Cache\FileCache; +use function assert; use function is_string; +/** @phpstan-import-type RouteData from DataGenerator */ final class FastRoute { + /** @var RouteData|null */ + private ?array $processedConfiguration = null; + /** * @param Closure(ConfigureRoutes):void $routeDefinitionCallback * @param class-string $routeParser @@ -110,6 +115,10 @@ public function useCustomDispatcher(string $dataGenerator, string $dispatcher): /** @return RouteData */ private function buildConfiguration(): array { + if ($this->processedConfiguration !== null) { + return $this->processedConfiguration; + } + $loader = function (): array { $configuredRoutes = new $this->routesConfiguration( new $this->routeParser(), @@ -122,7 +131,7 @@ private function buildConfiguration(): array }; if ($this->cacheDriver === null) { - return loader(); + return $this->processedConfiguration = $loader(); } assert(is_string($this->cacheKey)); @@ -131,11 +140,16 @@ private function buildConfiguration(): array ? new $this->cacheDriver() : $this->cacheDriver; - return $cache->get($this->cacheKey, $loader); + return $this->processedConfiguration = $cache->get($this->cacheKey, $loader); } public function dispatcher(): Dispatcher { return new $this->dispatcher($this->buildConfiguration()); } + + public function uriGenerator(): GenerateUri + { + return new GenerateUri\FromProcessedConfiguration($this->buildConfiguration()[2]); + } } diff --git a/test/RouteCollectorTest.php b/test/RouteCollectorTest.php index 99ff33f..fd71b39 100644 --- a/test/RouteCollectorTest.php +++ b/test/RouteCollectorTest.php @@ -127,7 +127,7 @@ private static function dummyDataGenerator(): DataGenerator /** @inheritDoc */ public function getData(): array { - return [[], []]; + return [[], [], []]; } /** @inheritDoc */