Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4e39d25

Browse files
committed
feature #46042 [Routing] Add params variable to condition expression (HypeMC)
This PR was merged into the 6.1 branch. Discussion ---------- [Routing] Add params variable to condition expression | Q | A | ------------- | --- | Branch? | 6.1 | Bug fix? | no | New feature? | yes | Deprecations? | yes | Tickets | - | License | MIT | Doc PR | TODO Currently it's not possible to use `request.attributes.get('_route_params')` in route conditions because the parameter is not yet set when the expression is being evaluated. That happens [after the matcher is finished](https://github.com/symfony/symfony/blob/6ecd17e7b249edad650f3fb6d9a126eee4e132f3/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php#L120). This PR adds a new `params` variable to the condition option expression syntax: ```php class FooController { #[Route('/foo/{id}', requirements: ['id' => '\d+'], condition: "params['id'] < 100")] public function index(int $id): Response { } } ``` Commits ------- acefeb9 [Routing] Add route_parameters variable to condition expression
2 parents 76b9219 + acefeb9 commit 4e39d25

10 files changed

+106
-28
lines changed

UPGRADE-6.2.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
UPGRADE FROM 6.1 to 6.2
2+
=======================
3+
4+
Routing
5+
-------
6+
7+
* Add argument `$routeParameters` to `UrlMatcher::handleRouteRequirements()`

src/Symfony/Component/Routing/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
CHANGELOG
22
=========
33

4+
6.2
5+
---
6+
7+
* Add `params` variable to condition expression
8+
* Deprecate not passing route parameters as the forth argument to `UrlMatcher::handleRouteRequirements()`
9+
410
6.1
511
---
612

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, \$params) { // \$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', 'params']);
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.2', 'The "%s()" method will have a new "array $routeParameters" argument in version 7.0, not defining it is deprecated.', __METHOD__);
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+
'params' => $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, $params) { // $checkCondition
2628
switch ($condition) {
2729
case -1: return ($context->getMethod() == "GET");
30+
case -2: return ($params["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("params['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', [], [], "params['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 \"params['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("params['_route'] matches '/^s[a-z]+$/'");
482+
$coll->add('static', $route);
483+
$route = new Route('/bar');
484+
$route->setHost('en.example.com');
485+
$route->setCondition("params['_route'] matches '/^s[a-z\-]+$/'");
486+
$coll->add('static-with-host', $route);
487+
$route = new Route('/foo/{id}');
488+
$route->setCondition("params['id'] < 100");
489+
$coll->add('dynamic1', $route);
490+
$route = new Route('/foo/{id}');
491+
$route->setCondition("params['id'] > 100 and params['id'] < 1000");
492+
$coll->add('dynamic2', $route);
493+
$route = new Route('/bar/{id}/');
494+
$route->setCondition("params['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)