From 91479c48d276d907cc68614b46409fa92ec4a1c8 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 --- CHANGELOG.md | 5 +++++ Matcher/UrlMatcher.php | 4 ++++ Route.php | 19 +++++++++++++++---- Tests/Matcher/UrlMatcherTest.php | 17 +++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a3f28a7..bb4f4baf 100644 --- a/CHANGELOG.md +++ b/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/Matcher/UrlMatcher.php b/Matcher/UrlMatcher.php index 72380332..09c1d299 100644 --- a/Matcher/UrlMatcher.php +++ b/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/Route.php b/Route.php index ac8d8bc6..abbc3990 100644 --- a/Route.php +++ b/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/Tests/Matcher/UrlMatcherTest.php b/Tests/Matcher/UrlMatcherTest.php index 78bf2b3d..d9cfa7b1 100644 --- a/Tests/Matcher/UrlMatcherTest.php +++ b/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());