diff --git a/src/Http/RouteCollection.php b/src/Http/RouteCollection.php index 4e962fe005..87fc44d35a 100644 --- a/src/Http/RouteCollection.php +++ b/src/Http/RouteCollection.php @@ -11,6 +11,7 @@ use FastRoute\DataGenerator; use FastRoute\RouteParser; +use Illuminate\Support\Arr; class RouteCollection { @@ -88,10 +89,25 @@ protected function fixPathPart(&$part, $key, array $parameters) public function getPath($name, array $parameters = []) { if (isset($this->reverse[$name])) { - $parts = $this->reverse[$name][0]; - array_walk($parts, [$this, 'fixPathPart'], $parameters); - - return '/'.ltrim(implode('', $parts), '/'); + $maxMatches = 0; + $matchingParts = $this->reverse[$name][0]; + + // For a given route name, we want to choose the option that best matches the given parameters. + // Each routing option is an array of parts. Each part is either a constant string + // (which we don't care about here), or an array where the first element is the parameter name + // and the second element is a regex into which the parameter value is inserted, if the parameter matches. + foreach ($this->reverse[$name] as $parts) { + foreach ($parts as $i => $part) { + if (is_array($part) && Arr::exists($parameters, $part[0]) && $i > $maxMatches) { + $maxMatches = $i; + $matchingParts = $parts; + } + } + } + + array_walk($matchingParts, [$this, 'fixPathPart'], $parameters); + + return '/'.ltrim(implode('', $matchingParts), '/'); } throw new \RuntimeException("Route $name not found"); diff --git a/tests/unit/Foundation/RouteCollectionTest.php b/tests/unit/Foundation/RouteCollectionTest.php new file mode 100644 index 0000000000..ac6eb26ca5 --- /dev/null +++ b/tests/unit/Foundation/RouteCollectionTest.php @@ -0,0 +1,76 @@ +expectException(RuntimeException::class); + + $collection->getPath('nonexistent'); + } + + /** @test */ + public function it_properly_processes_a_simple_route_with_no_parameters() + { + $collection = new RouteCollection(); + // We can use anything for the handler since we're only testing getPath + $collection->addRoute('GET', '/custom/route', 'custom', ''); + + $this->assertEquals('/custom/route', $collection->getPath('custom')); + } + + /** @test */ + public function it_properly_processes_a_route_with_all_parameters_required() + { + $collection = new RouteCollection(); + // We can use anything for the handler since we're only testing getPath + $collection->addRoute('GET', '/custom/{route}/{has}/{parameters}', 'custom', ''); + + $this->assertEquals('/custom/something/something_else/anything_else', $collection->getPath('custom', [ + 'route' => 'something', + 'has' => 'something_else', + 'parameters' => 'anything_else' + ])); + } + + /** @test */ + public function it_works_if_optional_parameters_are_missing() + { + $collection = new RouteCollection(); + // We can use anything for the handler since we're only testing getPath + $collection->addRoute('GET', '/custom/{route}[/{has}]', 'custom', ''); + + $this->assertEquals('/custom/something', $collection->getPath('custom', [ + 'route' => 'something' + ])); + } + + /** @test */ + public function it_works_with_optional_parameters() + { + $collection = new RouteCollection(); + // We can use anything for the handler since we're only testing getPath + $collection->addRoute('GET', '/custom/{route}[/{has}]', 'custom', ''); + + $this->assertEquals('/custom/something/something_else', $collection->getPath('custom', [ + 'route' => 'something', + 'has' => 'something_else' + ])); + } +}