Skip to content

Commit 15eceef

Browse files
committed
Fix path support after unlimited optional placeholders
When using a route which contains both an unlimited optional placeholder, and another optional placeholder afterwards, the incorrect values are collected. For example, given the following route: ``` /go/to/[{location:.*}[/info/{subpage}]] ``` The following behaviour is currently observed: - route: `/go/to/[{location:.*}[/info/{subpage}]]` - url: `/go/to/australia/perth/info/about` - location: `'australia/perth/info/about'` - subpage: `''` Note that the `location` contains `/info/about` and the `subpage` is empty. This is inconsistent with the behaviour where an unlimited value is _not_ used: - route: `/go/to/[{location}[/info/{subpage}]]` - url: `/go/to/australia/info/about` - location: `'australia'` - subpage: `'about'` In the case of the unlimited optional path, the expected behaviour is: The correct value would be: - route: `/go/to/[{location:.*}[/info/{subpage}]]` - url: `/go/to/australia/perth/info/about` - location: `'australia/perth'` - subpage: `'about'` This commit change updates the route dispatcher to reverse the order of the routes when adding routes to the router, which brings the unlimited path placeholder format inline with limited path placeholders. Signed-off-by: Andrew Nicols <[email protected]>
1 parent d3ada01 commit 15eceef

5 files changed

+66
-3
lines changed

src/GenerateUri/FromProcessedConfiguration.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
use function array_key_exists;
1010
use function array_keys;
11+
use function array_reverse;
1112
use function assert;
1213
use function count;
1314
use function is_string;
@@ -34,7 +35,7 @@ public function forRoute(string $name, array $substitutions = []): string
3435

3536
$missingParameters = [];
3637

37-
foreach ($this->processedConfiguration[$name] as $parsedRoute) {
38+
foreach (array_reverse($this->processedConfiguration[$name]) as $parsedRoute) {
3839
$missingParameters = $this->missingParameters($parsedRoute, $substitutions);
3940

4041
// Only attempt to generate the path if we have the necessary info

src/RouteCollector.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function __construct(
3131
public function addRoute(string|array $httpMethod, string $route, mixed $handler, array $extraParameters = []): void
3232
{
3333
$route = $this->currentGroupPrefix . $route;
34-
$parsedRoutes = $this->routeParser->parse($route);
34+
$parsedRoutes = array_reverse($this->routeParser->parse($route));
3535

3636
$extraParameters = [self::ROUTE_REGEX => $route] + $extraParameters;
3737

test/Dispatcher/DispatcherTestCase.php

+12
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,18 @@ public static function provideFoundDispatchCases(): iterable
329329
};
330330

331331
yield 'options method is supported' => ['OPTIONS', '/about', $callback, 'handler0', [], ['_route' => '/about']];
332+
333+
$callback = static function (ConfigureRoutes $r): void {
334+
$r->addRoute('GET', '/about[/{aboutwhat}[/location]]', 'handler0');
335+
};
336+
337+
yield 'Paths can be placed after an optional placeholder' => ['GET', '/about/some/location', $callback, 'handler0', ['aboutwhat' => 'some'], ['_route' => '/about[/{aboutwhat}[/location]]']];
338+
339+
$callback = static function (ConfigureRoutes $r): void {
340+
$r->addRoute('GET', '/about[/{aboutwhat:.*}[/location]]', 'handler0');
341+
};
342+
343+
yield 'Paths can be placed after an unlimited optional placeholder' => ['GET', '/about/the/nested/location', $callback, 'handler0', ['aboutwhat' => 'the/nested'], ['_route' => '/about[/{aboutwhat:.*}[/location]]']];
332344
}
333345

334346
/** @return iterable<string, array{string, string, Closure(ConfigureRoutes):void}> */

test/GenerateUri/FromProcessedConfigurationTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public function unicodeParametersAreAlsoAccepted(): void
129129
private static function routeGeneratorFor(array $routeMap): GenerateUri
130130
{
131131
$parseRoutes = static function (string $route): array {
132-
return array_reverse((new RouteParser\Std())->parse($route));
132+
return (new RouteParser\Std())->parse($route);
133133
};
134134

135135
return new GenerateUri\FromProcessedConfiguration(array_map($parseRoutes, $routeMap));

test/RouteCollectorTest.php

+50
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,26 @@ public function routesCanBeGrouped(): void
118118
self::assertSame($expected, $dataGenerator->routes);
119119
}
120120

121+
#[PHPUnit\Test]
122+
public function optionalRoutesCanBeUsed(): void
123+
{
124+
$dataGenerator = self::dummyNestedDataGenerator();
125+
$r = new RouteCollector(new Std(), $dataGenerator);
126+
127+
$r->any('/any[/{optional}]', 'optional');
128+
$r->get('/any[/{optional}/hello]', 'optional');
129+
130+
$expected = [
131+
['*', '/any/', ['optional', '[^/]+'], 'optional', ['_route' => '/any[/{optional}]']],
132+
['*', '/any', 'optional', ['_route' => '/any[/{optional}]']],
133+
['GET', '/any/', ['optional', '[^/]+'], '/hello', 'optional', ['_route' => '/any[/{optional}/hello]']],
134+
['GET', '/any', 'optional', ['_route' => '/any[/{optional}/hello]']],
135+
];
136+
137+
self::assertObjectHasProperty('routes', $dataGenerator);
138+
self::assertSame($expected, $dataGenerator->routes);
139+
}
140+
121141
#[PHPUnit\Test]
122142
public function namedRoutesShouldBeRegistered(): void
123143
{
@@ -180,4 +200,34 @@ public function addRoute(string $httpMethod, array $routeData, mixed $handler, a
180200
}
181201
};
182202
}
203+
204+
private static function dummyNestedDataGenerator(): DataGenerator
205+
{
206+
return new class implements DataGenerator
207+
{
208+
/** @var list<array{string, string, mixed, array<string, bool|float|int|string>}> */
209+
public array $routes = [];
210+
211+
/** @inheritDoc */
212+
public function getData(): array
213+
{
214+
return [[], []];
215+
}
216+
217+
/** @inheritDoc */
218+
public function addRoute(string $httpMethod, array $routeData, mixed $handler, array $extraParameters = []): void
219+
{
220+
// assert(count($routeData) === 1 && is_string($routeData[0]));
221+
222+
$route = [$httpMethod];
223+
224+
foreach ($routeData as $data) {
225+
$route[] = $data;
226+
}
227+
$route[] = $handler;
228+
$route[] = $extraParameters;
229+
$this->routes[] = $route;
230+
}
231+
};
232+
}
183233
}

0 commit comments

Comments
 (0)