8000 [Routing] Add route_parameters variable to condition expression · symfony/symfony@50fa6ef · GitHub
[go: up one dir, main page]

Skip to content
Sign in

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 50fa6ef

Browse files
committed
[Routing] Add route_parameters variable to condition expression
1 parent 2a38810 commit 50fa6ef

10 files changed

+100
-28
lines changed

UPGRADE-6.1.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ HttpKernel
3131

3232
* Deprecate StreamedResponseListener, it's not needed anymore
3333

34+
Routing
35+
-------
36+
37+
* Add argument `$routeParameters` to `UrlMatcher::handleRouteRequirements()`
38+
3439
Serializer
3540
----------
3641

src/Symfony/Component/Routing/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ CHANGELOG
1010
* Already encoded slashes are not decoded nor double-encoded anymore when generating URLs (query parameters)
1111
* Add `EnumRequirement` to help generate route requirements from a `\BackedEnum`
1212
* Add `Requirement`, a collection of universal regular-expression constants to use as route parameter requirements
13+
* Add `route_parameters` variable to condition option
14+
* Deprecate not passing route parameters as the forth argument to `UrlMatcher::handleRouteRequirements()`
1315

1416
5.3
1517
---

src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public function getCompiledRoutes(bool $forDump = false): array
115115
}
116116

117117
$checkConditionCode = <<<EOF
118-
static function (\$condition, \$context, \$request) { // \$checkCondition
118+
static function (\$condition, \$context, \$request, \$route_parameters) { // \$checkCondition
119119
switch (\$condition) {
120120
{$this->indent(implode("\n", $conditions), 3)}
121121
}
@@ -426,7 +426,7 @@ private function compileRoute(Route $route, string $name, string|array|null $var
426426
}
427427

428428
if ($condition = $route->getCondition()) {
429-
$condition = $this->getExpressionLanguage()->compile($condition, ['context', 'request']);
429+
$condition = $this->getExpressionLanguage()->compile($condition, ['context', 'request', 'route_parameters']);
430430
$condition = $conditions[$condition] ??= (str_contains($condition, '$request') ? 1 : -1) * \count($conditions);
431431
} else {
432432
$condition = null;

src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherTrait.php

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,6 @@ private function doMatch(string $pathinfo, array &$allow = [], array &$allowSche
9292
$supportsRedirections = 'GET' === $canonicalMethod && $this instanceof RedirectableUrlMatcherInterface;
9393

9494
foreach ($this->staticRoutes[$trimmedPathinfo] ?? [] as [$ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash, , $condition]) {
95-
if ($condition && !($this->checkCondition)($condition, $context, 0 < $condition ? $request ??= $this->request ?: $this->createRequest($pathinfo) : null)) {
96-
continue;
97-
}
98-
9995
if ($requiredHost) {
10096
if ('{' !== $requiredHost[0] ? $requiredHost !== $host : !preg_match($requiredHost, $host, $hostMatches)) {
10197
continue;
@@ -106,6 +102,10 @@ private function doMatch(string $pathinfo, array &$allow = [], array &$allowSche
106102
}
107103
}
108104

105+
if ($condition && !($this->checkCondition)($condition, $context, 0 < $condition ? $request ??= $this->request ?: $this->createRequest($pathinfo) : null, $ret)) {
106+
continue;
107+
}
108+
109109
if ('/' !== $pathinfo && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
110110
if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) {
111111
return $allow = $allowSchemes = [];
@@ -132,13 +132,8 @@ private function doMatch(string $pathinfo, array &$allow = [], array &$allowSche
132132
foreach ($this->regexpList as $offset => $regex) {
133133
while (preg_match($regex, $matchedPathinfo, $matches)) {
134134
foreach ($this->dynamicRoutes[$m = (int) $matches['MARK']] as [$ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash, $hasTrailingVar, $condition]) {
135-
if (null !== $condition) {
136-
if (0 === $condition) { // marks the last route in the regexp
137-
continue 3;
138-
}
139-
if (!($this->checkCondition)($condition, $context, 0 < $condition ? $request ??= $this->request ?: $this->createRequest($pathinfo) : null)) {
140-
continue;
141-
}
135+
if (0 === $condition) { // marks the last route in the regexp
136+
continue 3;
142137
}
143138

144139
$hasTrailingVar = $trimmedPathinfo !== $pathinfo && $hasTrailingVar;
@@ -151,17 +146,21 @@ private function doMatch(string $pathinfo, array &$allow = [], array &$allowSche
151146
}
152147
}
153148

154-
if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
155-
if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) {
156-
return $allow = $allowSchemes = [];
149+
foreach ($vars as $i => $v) {
150+
if (isset($matches[1 + $i])) {
151+
$ret[$v] = $matches[1 + $i];
157152
}
153+
}
154+
155+
if ($condition && !($this->checkCondition)($condition, $context, 0 < $condition ? $request ??= $this->request ?: $this->createRequest($pathinfo) : null, $ret)) {
158156
continue;
159157
}
160158

161-
foreach ($vars as $i => $v) {
162-
if (isset($matches[1 + $i])) {
163-
$ret[$v] = $matches[1 + $i];
159+
if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {
160+
if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) {
161+
return $allow = $allowSchemes = [];
164162
}
163+
continue;
165164
}
166165

167166
if ($requiredSchemes && !isset($requiredSchemes[$context->getScheme()])) {

src/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ protected function matchCollection(string $pathinfo, RouteCollection $routes): a
115115
continue;
116116
}
117117

118-
$status = $this->handleRouteRequirements($pathinfo, $name, $route);
118+
$attributes = $this->getAttributes($route, $name, array_replace($matches, $hostMatches));
119+
120+
$status = $this->handleRouteRequirements($pathinfo, $name, $route, $attributes);
119121

120122
if (self::REQUIREMENT_MISMATCH === $status[0]) {
121123
$this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $route->getCondition()), self::ROUTE_ALMOST_MATCHES, $name, $route);
@@ -146,7 +148,7 @@ protected function matchCollection(string $pathinfo, RouteCollection $routes): a
146148

147149
$this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route);
148150

149-
return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, $status[1] ?? []));
151+
return array_replace($attributes, $status[1] ?? []);
150152
}
151153

152154
return [];

src/Symfony/Component/Routing/Matcher/UrlMatcher.php

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,9 @@ protected function matchCollection(string $pathinfo, RouteCollection $routes): a
167167
continue;
168168
}
169169

170-
$status = $this->handleRouteRequirements($pathinfo, $name, $route);
170+
$attributes = $this->getAttributes($route, $name, array_replace($matches, $hostMatches));
171+
172+
$status = $this->handleRouteRequirements($pathinfo, $name, $route, $attributes);
171173

172174
if (self::REQUIREMENT_MISMATCH === $status[0]) {
173175
continue;
@@ -190,7 +192,7 @@ protected function matchCollection(string $pathinfo, RouteCollection $routes): a
190192
continue;
191193
}
192194

193-
return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, $status[1] ?? []));
195+
return array_replace($attributes, $status[1] ?? []);
194196
}
195197

196198
return [];
@@ -220,10 +222,25 @@ protected function getAttributes(Route $route, string $name, array $attributes):
220222
*
221223
* @return array The first element represents the status, the second contains additional information
222224
*/
223-
protected function handleRouteRequirements(string $pathinfo, string $name, Route $route): array
225+
protected function handleRouteRequirements(string $pathinfo, string $name, Route $route/*, array $routeParameters*/): array
224226
{
227+
if (\func_num_args() < 4) {
228+
trigger_deprecation('symfony/routing', '6.1', 'The "%s()" method will have a new "array $routeParameters" argument in version 7.0, not defining it is deprecated.', __M F438 ETHOD__);
229+
$routeParameters = [];
230+
} else {
231+
$routeParameters = func_get_arg(3);
232+
233+
if (!\is_array($routeParameters)) {
234+
throw new \TypeError(sprintf('"%s": Argument $routeParameters is expected to be an array, got "%s".', __METHOD__, get_debug_type($routeParameters)));
235+
}
236+
}
237+
225238
// expression condition
226-
if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), ['context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)])) {
239+
if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), [
240+
'context' => $this->context,
241+
'request' => $this->request ?: $this->createRequest($pathinfo),
242+
'route_parameters' => $routeParameters,
243+
])) {
227244
return [self::REQUIREMENT_MISMATCH, null];
228245
}
229246

src/Symfony/Component/Routing/Tests/Fixtures/dumper/compiled_url_matcher3.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,20 @@
1414
[ // $regexpList
1515
0 => '{^(?'
1616
.'|/rootprefix/([^/]++)(*:27)'
17+
.'|/with\\-condition/(\\d+)(*:56)'
1718
.')/?$}sD',
1819
],
1920
[ // $dynamicRoutes
20-
27 => [
21-
[['_route' => 'dynamic'], ['var'], null, null, false, true, null],
21+
27 => [[['_route' => 'dynamic'], ['var'], null, null, false, true, null]],
22+
56 => [
23+
[['_route' => 'with-condition-dynamic'], ['id'], null, null, false, true, -2],
2224
[null, null, null, null, false, false, 0],
2325
],
2426
],
25-
static function ($condition, $context, $request) { // $checkCondition
27+
static function ($condition, $context, $request, $route_parameters) { // $checkCondition
2628
switch ($condition) {
2729
case -1: return ($context->getMethod() == "GET");
30+
case -2: return ($route_parameters["id"] < 100);
2831
}
2932
},
3033
];

src/Symfony/Component/Routing/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,10 @@ public function getRouteCollections()
296296
$route = new Route('/with-condition');
297297
$route->setCondition('context.getMethod() == "GET"');
298298
$rootprefixCollection->add('with-condition', $route);
299+
$route = new Route('/with-condition/{id}');
300+
$route->setRequirement('id', '\d+');
301+
$route->setCondition("route_parameters['id'] < 100");
302+
$rootprefixCollection->add('with-condition-dynamic', $route);
299303

300304
/* test case 4 */
301305
$headMatchCasesCollection = new RouteCollection();

src/Symfony/Component/Routing/Tests/Matcher/TraceableUrlMatcherTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ public function testRoutesWithConditions()
104104
{
105105
$routes = new RouteCollection();
106106
$routes->add('foo', new Route('/foo', [], [], [], 'baz', [], [], "request.headers.get('User-Agent') matches '/firefox/i'"));
107+
$routes->add('bar', new Route('/bar/{id}', [], [], [], 'baz', [], [], "route_parameters['id'] < 100"));
107108

108109
$context = new RequestContext();
109110
$context->setHost('baz');
@@ -117,6 +118,14 @@ public function testRoutesWithConditions()
117118
$matchingRequest = Request::create('/foo', 'GET', [], [], [], ['HTTP_USER_AGENT' => 'Firefox']);
118119
$traces = $matcher->getTracesForRequest($matchingRequest);
119120
$this->assertEquals('Route matches!', $traces[0]['log']);
121+
122+
$notMatchingRequest = Request::create('/bar/1000', 'GET');
123+
$traces = $matcher->getTracesForRequest($notMatchingRequest);
124+
$this->assertEquals("Condition \"route_parameters['id'] < 100\" does not evaluate to \"true\"", $traces[1]['log']);
125+
126+
$matchingRequest = Request::create('/bar/10', 'GET');
127+
$traces = $matcher->getTracesForRequest($matchingRequest);
128+
$this->assertEquals('Route matches!', $traces[1]['log']);
120129
}
121130

122131
protected function getUrlMatcher(RouteCollection $routes, RequestContext $context = null)

src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,37 @@ public function testRequestCondition()
474474
$this->assertEquals(['bar' => 'bar', '_route' => 'foo'], $matcher->match('/foo/bar'));
475475
}
476476

477+
public function testRouteParametersCondition()
478+
{
479+
$coll = new RouteCollection();
480+
$route = new Route('/foo');
481+
$route->setCondition("route_parameters['_route'] matches '/^s[a-z]+$/'");
482+
$coll->add('static', $route);
483+
$route = new Route('/bar');
484+
$route->setHost('en.example.com');
485+
$route->setCondition("route_parameters['_route'] matches '/^s[a-z\-]+$/'");
486+
$coll->add('static-with-host', $route);
487+
$route = new Route('/foo/{id}');
488+
$route->setCondition("route_parameters['id'] < 100");
489+
$coll->add('dynamic1', $route);
490+
$route = new Route('/foo/{id}');
491+
$route->setCondition("route_parameters['id'] > 100 and route_parameters['id'] < 1000");
492+
$coll->add('dynamic2', $route);
493+
$route = new Route('/bar/{id}/');
494+
$route->setCondition("route_parameters['id'] < 100");
495+
$coll->add('dynamic-with-slash', $route);
496+
$matcher = $this->getUrlMatcher($coll, new RequestContext('/sub/front.php', 'GET', 'en.example.com'));
497+
498+
$this->assertEquals(['_route' => 'static'], $matcher->match('/foo'));
499+
$this->assertEquals(['_route' => 'static-with-host'], $matcher->match('/bar'));
500+
$this->assertEquals(['_route' => 'dynamic1', 'id' => '10'], $matcher->match('/foo/10'));
501+
$this->assertEquals(['_route' => 'dynamic2', 'id' => '200'], $matcher->match('/foo/200'));
502+
$this->assertEquals(['_route' => 'dynamic-with-slash', 'id' => '10'], $matcher->match('/bar/10/'));
503+
504+
$this->expectException(ResourceNotFoundException::class);
505+
$matcher->match('/foo/3000');
506+
}
507+
477508
public function testDecodeOnce()
478509
{
479510
$coll = new RouteCollection();

0 commit comments

Comments
 (0)
0