diff --git a/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php b/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php index a957af8c0c0a8..689d08122afbe 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php @@ -110,7 +110,33 @@ public function onKernelRequest(RequestEvent $event): void 'method' => $request->getMethod(), ]); - $request->attributes->add($parameters); + $attributes = $parameters; + if ($mapping = $parameters['_route_mapping'] ?? false) { + unset($parameters['_route_mapping']); + $mappedAttributes = []; + $attributes = []; + + foreach ($parameters as $parameter => $value) { + $attribute = $mapping[$parameter] ?? $parameter; + + if (!isset($mappedAttributes[$attribute])) { + $attributes[$attribute] = $value; + $mappedAttributes[$attribute] = $parameter; + } elseif ('' !== $mappedAttributes[$attribute]) { + $attributes[$attribute] = [ + $mappedAttributes[$attribute] => $attributes[$attribute], + $parameter => $value, + ]; + $mappedAttributes[$attribute] = ''; + } else { + $attributes[$attribute][$parameter] = $value; + } + } + + $attributes['_route_mapping'] = $mapping; + } + + $request->attributes->add($attributes); unset($parameters['_route'], $parameters['_controller']); $request->attributes->set('_route_params', $parameters); } catch (ResourceNotFoundException $e) { diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php index e68461a18cfaf..d13093db0c55c 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php @@ -264,4 +264,64 @@ public function testMethodNotAllowedException() $listener = new RouterListener($urlMatcher, new RequestStack()); $listener->onKernelRequest($event); } + + /** + * @dataProvider provideRouteMapping + */ + public function testRouteMapping(array $expected, array $parameters) + { + $kernel = $this->createMock(HttpKernelInterface::class); + $request = Request::create('http://localhost/'); + $event = new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST); + + $requestMatcher = $this->createMock(RequestMatcherInterface::class); + $requestMatcher->expects($this->any()) + ->method('matchRequest') + ->with($this->isInstanceOf(Request::class)) + ->willReturn($parameters); + + $listener = new RouterListener($requestMatcher, new RequestStack(), new RequestContext()); + $listener->onKernelRequest($event); + + $expected['_route_mapping'] = $parameters['_route_mapping']; + unset($parameters['_route_mapping']); + $expected['_route_params'] = $parameters; + + $this->assertEquals($expected, $request->attributes->all()); + } + + public static function provideRouteMapping(): iterable + { + yield [ + [ + 'conference' => 'vienna-2024', + ], + [ + 'slug' => 'vienna-2024', + '_route_mapping' => [ + 'slug' => 'conference', + ], + ], + ]; + + yield [ + [ + 'article' => [ + 'id' => 'abc123', + 'date' => '2024-04-24', + 'slug' => 'symfony-rocks', + ], + ], + [ + 'id' => 'abc123', + 'date' => '2024-04-24', + 'slug' => 'symfony-rocks', + '_route_mapping' => [ + 'id' => 'article', + 'date' => 'article', + 'slug' => 'article', + ], + ], + ]; + } } diff --git a/src/Symfony/Component/Routing/CHANGELOG.md b/src/Symfony/Component/Routing/CHANGELOG.md index 0a3f28a7672c8..bb4f4baf22214 100644 --- a/src/Symfony/Component/Routing/CHANGELOG.md +++ b/src/Symfony/Component/Routing/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Add `{foo:bar}` syntax to define a mapping between a route parameter and its corresponding request attribute + 7.0 --- diff --git a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php index 723803323dbe7..09c1d29967cc0 100644 --- a/src/Symfony/Component/Routing/Matcher/UrlMatcher.php +++ b/src/Symfony/Component/Routing/Matcher/UrlMatcher.php @@ -197,6 +197,10 @@ protected function getAttributes(Route $route, string $name, array $attributes): } $attributes['_route'] = $name; + if ($mapping = $route->getOption('mapping')) { + $attributes['_route_mapping'] = $mapping; + } + return $this->mergeDefaults($attributes, $defaults); } diff --git a/src/Symfony/Component/Routing/Route.php b/src/Symfony/Component/Routing/Route.php index ac8d8bc6e9086..abbc39907ccf4 100644 --- a/src/Symfony/Component/Routing/Route.php +++ b/src/Symfony/Component/Routing/Route.php @@ -412,20 +412,31 @@ public function compile(): CompiledRoute private function extractInlineDefaultsAndRequirements(string $pattern): string { - if (false === strpbrk($pattern, '?<')) { + if (false === strpbrk($pattern, '?<:')) { return $pattern; } - return preg_replace_callback('#\{(!?)([\w\x80-\xFF]++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) { + $mapping = $this->getDefault('_route_mapping') ?? []; + + $pattern = preg_replace_callback('#\{(!?)([\w\x80-\xFF]++)(:[\w\x80-\xFF]++)?(<.*?>)?(\?[^\}]*+)?\}#', function ($m) use (&$mapping) { + if (isset($m[5][0])) { + $this->setDefault($m[2], '?' !== $m[5] ? substr($m[5], 1) : null); + } if (isset($m[4][0])) { - $this->setDefault($m[2], '?' !== $m[4] ? substr($m[4], 1) : null); + $this->setRequirement($m[2], substr($m[4], 1, -1)); } if (isset($m[3][0])) { - $this->setRequirement($m[2], substr($m[3], 1, -1)); + $mapping[$m[2]] = substr($m[3], 1); } return '{'.$m[1].$m[2].'}'; }, $pattern); + + if ($mapping) { + $this->setDefault('_route_mapping', $mapping); + } + + return $pattern; } private function sanitizeRequirement(string $key, string $regex): string diff --git a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php index 78bf2b3d75a6a..d9cfa7b1bd57a 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php @@ -1000,6 +1000,23 @@ public function testUtf8VarName() $this->assertEquals(['_route' => 'foo', 'bär' => 'baz', 'bäz' => 'foo'], $matcher->match('/foo/baz')); } + public function testMapping() + { + $collection = new RouteCollection(); + $collection->add('a', new Route('/conference/{slug:conference}')); + + $matcher = $this->getUrlMatcher($collection); + + $expected = [ + '_route' => 'a', + 'slug' => 'vienna-2024', + '_route_mapping' => [ + 'slug' => 'conference', + ], + ]; + $this->assertEquals($expected, $matcher->match('/conference/vienna-2024')); + } + protected function getUrlMatcher(RouteCollection $routes, ?RequestContext $context = null) { return new UrlMatcher($routes, $context ?? new RequestContext()); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy