Content-Length: 9091 | pFad | http://github.com/symfony/symfony/pull/54720.patch
thub.com
From 1e091b9da3ba67f3baf6e8831b2c7ff712f442c6 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas
Date: Wed, 24 Apr 2024 11:31:47 +0200
Subject: [PATCH] [Routing] Add `{foo:bar}` syntax to define a mapping between
a route parameter and its corresponding request attribute
---
.../EventListener/RouterListener.php | 28 ++++++++-
.../EventListener/RouterListenerTest.php | 60 +++++++++++++++++++
src/Symfony/Component/Routing/CHANGELOG.md | 5 ++
.../Component/Routing/Matcher/UrlMatcher.php | 4 ++
src/Symfony/Component/Routing/Route.php | 19 ++++--
.../Routing/Tests/Matcher/UrlMatcherTest.php | 17 ++++++
6 files changed, 128 insertions(+), 5 deletions(-)
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());
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/symfony/symfony/pull/54720.patch
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy