From 9cd8dd37a5b04b1e2f2a111fc6f3d7b4337f5ed8 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 22 May 2025 09:35:49 +0200 Subject: [PATCH 01/25] Allow query-specific parameters in URL generator using `_query` --- CHANGELOG.md | 5 ++ Generator/UrlGenerator.php | 13 +++++ Tests/Generator/UrlGeneratorTest.php | 74 ++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d21e550f..4ef96d53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.4 +--- + + * Allow query-specific parameters in `UrlGenerator` using `_query` + 7.3 --- diff --git a/Generator/UrlGenerator.php b/Generator/UrlGenerator.php index 216b0d54..d82b9189 100644 --- a/Generator/UrlGenerator.php +++ b/Generator/UrlGenerator.php @@ -142,6 +142,18 @@ public function generate(string $name, array $parameters = [], int $referenceTyp */ protected function doGenerate(array $variables, array $defaults, array $requirements, array $tokens, array $parameters, string $name, int $referenceType, array $hostTokens, array $requiredSchemes = []): string { + $queryParameters = []; + + if (isset($parameters['_query'])) { + if (\is_array($parameters['_query'])) { + $queryParameters = $parameters['_query']; + unset($parameters['_query']); + } else { + trigger_deprecation('symfony/routing', '7.4', 'Parameter "_query" is reserved for passing an array of query parameters. Passing a scalar value is deprecated and will throw an exception in Symfony 8.0.'); + // throw new InvalidParameterException('Parameter "_query" must be an array of query parameters.'); + } + } + $variables = array_flip($variables); $mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters); @@ -260,6 +272,7 @@ protected function doGenerate(array $variables, array $defaults, array $requirem // add a query string if needed $extra = array_udiff_assoc(array_diff_key($parameters, $variables), $defaults, fn ($a, $b) => $a == $b ? 0 : 1); + $extra = array_merge($extra, $queryParameters); array_walk_recursive($extra, $caster = static function (&$v) use (&$caster) { if (\is_object($v)) { diff --git a/Tests/Generator/UrlGeneratorTest.php b/Tests/Generator/UrlGeneratorTest.php index 25a4c674..27af7679 100644 --- a/Tests/Generator/UrlGeneratorTest.php +++ b/Tests/Generator/UrlGeneratorTest.php @@ -1054,6 +1054,80 @@ public function testUtf8VarName() $this->assertSame('/app.php/foo/baz', $this->getGenerator($routes)->generate('test', ['bär' => 'baz'])); } + public function testQueryParameters() + { + $routes = $this->getRoutes('user', new Route('/user/{username}')); + $url = $this->getGenerator($routes)->generate('user', [ + 'username' => 'john', + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'baz', + '_query' => [ + 'a' => '123', + 'd' => '789', + ], + ]); + $this->assertSame('/app.php/user/john?a=123&b=bar&c=baz&d=789', $url); + } + + public function testRouteHostParameterAndQueryParameterWithSameName() + { + $routes = $this->getRoutes('admin_stats', new Route('/admin/stats', requirements: ['domain' => '.+'], host: '{siteCode}.{domain}')); + $url = $this->getGenerator($routes)->generate('admin_stats', [ + 'siteCode' => 'fr', + 'domain' => 'example.com', + '_query' => [ + 'siteCode' => 'us', + ], + ], UrlGeneratorInterface::NETWORK_PATH); + $this->assertSame('//fr.example.com/app.php/admin/stats?siteCode=us', $url); + } + + public function testRoutePathParameterAndQueryParameterWithSameName() + { + $routes = $this->getRoutes('user', new Route('/user/{id}')); + $url = $this->getGenerator($routes)->generate('user', [ + 'id' => '123', + '_query' => [ + 'id' => '456', + ], + ]); + $this->assertSame('/app.php/user/123?id=456', $url); + } + + public function testQueryParameterCannotSubstituteRouteParameter() + { + $routes = $this->getRoutes('user', new Route('/user/{id}')); + + $this->expectException(MissingMandatoryParametersException::class); + $this->expectExceptionMessage('Some mandatory parameters are missing ("id") to generate a URL for route "user".'); + + $this->getGenerator($routes)->generate('user', [ + '_query' => [ + 'id' => '456', + ], + ]); + } + + /** + * @group legacy + */ + public function testQueryParametersWithScalarValue() + { + $routes = $this->getRoutes('user', new Route('/user/{id}')); + + $this->expectDeprecation( + 'Since symfony/routing 7.4: Parameter "_query" is reserved for passing an array of query parameters. ' . + 'Passing a scalar value is deprecated and will throw an exception in Symfony 8.0.', + ); + + $url = $this->getGenerator($routes)->generate('user', [ + 'id' => '123', + '_query' => 'foo', + ]); + $this->assertSame('/app.php/user/123?_query=foo', $url); + } + protected function getGenerator(RouteCollection $routes, array $parameters = [], $logger = null, ?string $defaultLocale = null) { $context = new RequestContext('/app.php'); From c71567120c75759c0803673129b0aa2c0684afe6 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 2 Jun 2025 16:08:14 +0200 Subject: [PATCH 02/25] Allow Symfony ^8.0 --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 59e30bef..1fcc24b6 100644 --- a/composer.json +++ b/composer.json @@ -20,11 +20,11 @@ "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "symfony/config": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", "psr/log": "^1|^2|^3" }, "conflict": { From 608459eef42b6f4cf9e7dc34a9529e48c99bd56f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 2 Jun 2025 17:50:55 +0200 Subject: [PATCH 03/25] Bump Symfony 8 to PHP >= 8.4 --- composer.json | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/composer.json b/composer.json index 1fcc24b6..d6588faf 100644 --- a/composer.json +++ b/composer.json @@ -16,21 +16,16 @@ } ], "require": { - "php": ">=8.2", + "php": ">=8.4", "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "psr/log": "^1|^2|^3" - }, - "conflict": { - "symfony/config": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/yaml": "<6.4" + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" }, "autoload": { "psr-4": { "Symfony\\Component\\Routing\\": "" }, From 589b5de87da2f3afaf5e0c23f28498d313c391b3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 4 Jun 2025 18:31:05 +0200 Subject: [PATCH 04/25] Enforce return types on all components --- Loader/AttributeClassLoader.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Loader/AttributeClassLoader.php b/Loader/AttributeClassLoader.php index 254582bf..04d1db17 100644 --- a/Loader/AttributeClassLoader.php +++ b/Loader/AttributeClassLoader.php @@ -273,10 +273,8 @@ public function getResolver(): LoaderResolverInterface /** * Gets the default route name for a class method. - * - * @return string */ - protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method) + protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method): string { $name = str_replace('\\', '_', $class->name).'_'.$method->name; $name = \function_exists('mb_strtolower') && preg_match('//u', $name) ? mb_strtolower($name, 'UTF-8') : strtolower($name); @@ -375,10 +373,8 @@ protected function createRoute(string $path, array $defaults, array $requirement /** * @param RouteAttribute $attr or an object that exposes a similar interface - * - * @return void */ - abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $attr); + abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $attr): void; /** * @return iterable From 1366ed8a23adefd546ceb975f70ad6a16520120c Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sat, 14 Jun 2025 22:01:30 +0200 Subject: [PATCH 05/25] replace expectDeprecation() with expectUserDeprecationMessage() --- Tests/Generator/UrlGeneratorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Generator/UrlGeneratorTest.php b/Tests/Generator/UrlGeneratorTest.php index 27af7679..75196bd2 100644 --- a/Tests/Generator/UrlGeneratorTest.php +++ b/Tests/Generator/UrlGeneratorTest.php @@ -1116,7 +1116,7 @@ public function testQueryParametersWithScalarValue() { $routes = $this->getRoutes('user', new Route('/user/{id}')); - $this->expectDeprecation( + $this->expectUserDeprecationMessage( 'Since symfony/routing 7.4: Parameter "_query" is reserved for passing an array of query parameters. ' . 'Passing a scalar value is deprecated and will throw an exception in Symfony 8.0.', ); From 12f7de4a4fc75f705856f160917f48f62a201507 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Sat, 21 Jun 2025 13:27:44 +0200 Subject: [PATCH 06/25] [Routing] Throw exception for non-array _query parameter --- CHANGELOG.md | 5 +++++ Generator/UrlGenerator.php | 3 +-- Tests/Generator/UrlGeneratorTest.php | 11 ++--------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ef96d53..1c9b7453 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +8.0 +--- + + * Providing a non-array `_query` parameter to `UrlGenerator` causes an `InvalidParameterException` + 7.4 --- diff --git a/Generator/UrlGenerator.php b/Generator/UrlGenerator.php index d82b9189..32d57e9c 100644 --- a/Generator/UrlGenerator.php +++ b/Generator/UrlGenerator.php @@ -149,8 +149,7 @@ protected function doGenerate(array $variables, array $defaults, array $requirem $queryParameters = $parameters['_query']; unset($parameters['_query']); } else { - trigger_deprecation('symfony/routing', '7.4', 'Parameter "_query" is reserved for passing an array of query parameters. Passing a scalar value is deprecated and will throw an exception in Symfony 8.0.'); - // throw new InvalidParameterException('Parameter "_query" must be an array of query parameters.'); + throw new InvalidParameterException('Parameter "_query" must be an array of query parameters.'); } } diff --git a/Tests/Generator/UrlGeneratorTest.php b/Tests/Generator/UrlGeneratorTest.php index 75196bd2..72eb1af7 100644 --- a/Tests/Generator/UrlGeneratorTest.php +++ b/Tests/Generator/UrlGeneratorTest.php @@ -1109,23 +1109,16 @@ public function testQueryParameterCannotSubstituteRouteParameter() ]); } - /** - * @group legacy - */ public function testQueryParametersWithScalarValue() { $routes = $this->getRoutes('user', new Route('/user/{id}')); - $this->expectUserDeprecationMessage( - 'Since symfony/routing 7.4: Parameter "_query" is reserved for passing an array of query parameters. ' . - 'Passing a scalar value is deprecated and will throw an exception in Symfony 8.0.', - ); + $this->expectException(InvalidParameterException::class); - $url = $this->getGenerator($routes)->generate('user', [ + $this->getGenerator($routes)->generate('user', [ 'id' => '123', '_query' => 'foo', ]); - $this->assertSame('/app.php/user/123?_query=foo', $url); } protected function getGenerator(RouteCollection $routes, array $parameters = [], $logger = null, ?string $defaultLocale = null) From 6d44831e1acd8b4a3fc8988e418696ecf2a9090b Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Sat, 21 Jun 2025 13:16:44 +0200 Subject: [PATCH 07/25] [Routing] Remove deprecated AttributeClassLoader property and setter --- CHANGELOG.md | 1 + Loader/AttributeClassLoader.php | 23 ++--------------------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c9b7453..5aa63900 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Providing a non-array `_query` parameter to `UrlGenerator` causes an `InvalidParameterException` + * Remove the protected `AttributeClassLoader::$routeAnnotationClass` property and the `setRouteAnnotationClass()` method, use `AttributeClassLoader::setRouteAttributeClass()` instead 7.4 --- diff --git a/Loader/AttributeClassLoader.php b/Loader/AttributeClassLoader.php index 04d1db17..58494045 100644 --- a/Loader/AttributeClassLoader.php +++ b/Loader/AttributeClassLoader.php @@ -55,10 +55,6 @@ */ abstract class AttributeClassLoader implements LoaderInterface { - /** - * @deprecated since Symfony 7.2, use "setRouteAttributeClass()" instead. - */ - protected string $routeAnnotationClass = RouteAttribute::class; private string $routeAttributeClass = RouteAttribute::class; protected int $defaultRouteIndex = 0; @@ -67,24 +63,11 @@ public function __construct( ) { } - /** - * @deprecated since Symfony 7.2, use "setRouteAttributeClass(string $class)" instead - * - * Sets the annotation class to read route properties from. - */ - public function setRouteAnnotationClass(string $class): void - { - trigger_deprecation('symfony/routing', '7.2', 'The "%s()" method is deprecated, use "%s::setRouteAttributeClass()" instead.', __METHOD__, self::class); - - $this->setRouteAttributeClass($class); - } - /** * Sets the attribute class to read route properties from. */ public function setRouteAttributeClass(string $class): void { - $this->routeAnnotationClass = $class; $this->routeAttributeClass = $class; } @@ -293,8 +276,7 @@ protected function getGlobals(\ReflectionClass $class): array { $globals = $this->resetGlobals(); - // to be replaced in Symfony 8.0 by $this->routeAttributeClass - if ($attribute = $class->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) { + if ($attribute = $class->getAttributes($this->routeAttributeClass, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) { $attr = $attribute->newInstance(); if (null !== $attr->getName()) { @@ -381,8 +363,7 @@ abstract protected function configureRoute(Route $route, \ReflectionClass $class */ private function getAttributes(\ReflectionClass|\ReflectionMethod $reflection): iterable { - // to be replaced in Symfony 8.0 by $this->routeAttributeClass - foreach ($reflection->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + foreach ($reflection->getAttributes($this->routeAttributeClass, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { yield $attribute->newInstance(); } } From 2a7b4777f5ae8258bbb47e811515d27d2cb9f307 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 8 Jul 2025 11:08:29 +0200 Subject: [PATCH 08/25] Various CS fixes --- Tests/Generator/UrlGeneratorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Generator/UrlGeneratorTest.php b/Tests/Generator/UrlGeneratorTest.php index 75196bd2..d513b318 100644 --- a/Tests/Generator/UrlGeneratorTest.php +++ b/Tests/Generator/UrlGeneratorTest.php @@ -1117,7 +1117,7 @@ public function testQueryParametersWithScalarValue() $routes = $this->getRoutes('user', new Route('/user/{id}')); $this->expectUserDeprecationMessage( - 'Since symfony/routing 7.4: Parameter "_query" is reserved for passing an array of query parameters. ' . + 'Since symfony/routing 7.4: Parameter "_query" is reserved for passing an array of query parameters. '. 'Passing a scalar value is deprecated and will throw an exception in Symfony 8.0.', ); From 74f73c5a5e7a20981990618176f4749277911abd Mon Sep 17 00:00:00 2001 From: Gregor Harlan Date: Sat, 12 Jul 2025 15:55:19 +0200 Subject: [PATCH 09/25] optimize `in_array` calls --- Loader/AttributeFileLoader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loader/AttributeFileLoader.php b/Loader/AttributeFileLoader.php index 3214d589..2c52d239 100644 --- a/Loader/AttributeFileLoader.php +++ b/Loader/AttributeFileLoader.php @@ -115,7 +115,7 @@ protected function findClass(string $file): string|false if (\T_DOUBLE_COLON === $tokens[$j][0] || \T_NEW === $tokens[$j][0]) { $skipClassToken = true; break; - } elseif (!\in_array($tokens[$j][0], [\T_WHITESPACE, \T_DOC_COMMENT, \T_COMMENT])) { + } elseif (!\in_array($tokens[$j][0], [\T_WHITESPACE, \T_DOC_COMMENT, \T_COMMENT], true)) { break; } } From aff4fac9b6d5ee2949cad4d6c688822d96b9b9a4 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 24 Jul 2025 14:45:41 +0200 Subject: [PATCH 10/25] Fix typos --- Tests/Fixtures/dumper/compiled_url_matcher13.php | 6 +++--- Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/Fixtures/dumper/compiled_url_matcher13.php b/Tests/Fixtures/dumper/compiled_url_matcher13.php index 63252943..466550c3 100644 --- a/Tests/Fixtures/dumper/compiled_url_matcher13.php +++ b/Tests/Fixtures/dumper/compiled_url_matcher13.php @@ -11,15 +11,15 @@ ], [ // $regexpList 0 => '{^(?' - .'|(?i:([^\\.]++)\\.exampple\\.com)\\.(?' + .'|(?i:([^\\.]++)\\.example\\.com)\\.(?' .'|/abc([^/]++)(?' - .'|(*:56)' + .'|(*:55)' .')' .')' .')/?$}sD', ], [ // $dynamicRoutes - 56 => [ + 55 => [ [['_route' => 'r1'], ['foo', 'foo'], null, null, false, true, null], [['_route' => 'r2'], ['foo', 'foo'], null, null, false, true, null], [null, null, null, null, false, false, 0], diff --git a/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php b/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php index d6be915a..1bb2c2e3 100644 --- a/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php +++ b/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php @@ -439,8 +439,8 @@ public static function getRouteCollections() /* test case 13 */ $hostCollection = new RouteCollection(); - $hostCollection->add('r1', (new Route('abc{foo}'))->setHost('{foo}.exampple.com')); - $hostCollection->add('r2', (new Route('abc{foo}'))->setHost('{foo}.exampple.com')); + $hostCollection->add('r1', (new Route('abc{foo}'))->setHost('{foo}.example.com')); + $hostCollection->add('r2', (new Route('abc{foo}'))->setHost('{foo}.example.com')); /* test case 14 */ $fixedLocaleCollection = new RouteCollection(); From 8bd9f39190e4f5114031362700a1c034b7ca9e3d Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 9 Oct 2024 11:06:51 +0200 Subject: [PATCH 11/25] run tests using PHPUnit 11.5 --- .../Dumper/CompiledUrlGeneratorDumperTest.php | 20 ++++++--------- Tests/Generator/UrlGeneratorTest.php | 25 ++++++++----------- phpunit.xml.dist | 11 +++++--- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/Tests/Generator/Dumper/CompiledUrlGeneratorDumperTest.php b/Tests/Generator/Dumper/CompiledUrlGeneratorDumperTest.php index 8edc49a6..d0e35e81 100644 --- a/Tests/Generator/Dumper/CompiledUrlGeneratorDumperTest.php +++ b/Tests/Generator/Dumper/CompiledUrlGeneratorDumperTest.php @@ -11,8 +11,9 @@ namespace Symfony\Component\Routing\Tests\Generator\Dumper; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectUserDeprecationMessageTrait; use Symfony\Component\Routing\Exception\RouteCircularReferenceException; use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Component\Routing\Generator\CompiledUrlGenerator; @@ -24,8 +25,6 @@ class CompiledUrlGeneratorDumperTest extends TestCase { - use ExpectUserDeprecationMessageTrait; - private RouteCollection $routeCollection; private CompiledUrlGeneratorDumper $generatorDumper; private string $testTmpFilepath; @@ -338,9 +337,8 @@ public function testIndirectCircularReferenceShouldThrowAnException() $this->generatorDumper->dump(); } - /** - * @group legacy - */ + #[IgnoreDeprecations] + #[Group('legacy')] public function testDeprecatedAlias() { $this->expectUserDeprecationMessage('Since foo/bar 1.0.0: The "b" route alias is deprecated. You should stop using it, as it will be removed in the future.'); @@ -356,9 +354,8 @@ public function testDeprecatedAlias() $compiledUrlGenerator->generate('b'); } - /** - * @group legacy - */ + #[IgnoreDeprecations] + #[Group('legacy')] public function testDeprecatedAliasWithCustomMessage() { $this->expectUserDeprecationMessage('Since foo/bar 1.0.0: foo b.'); @@ -374,9 +371,8 @@ public function testDeprecatedAliasWithCustomMessage() $compiledUrlGenerator->generate('b'); } - /** - * @group legacy - */ + #[IgnoreDeprecations] + #[Group('legacy')] public function testTargettingADeprecatedAliasShouldTriggerDeprecation() { $this->expectUserDeprecationMessage('Since foo/bar 1.0.0: foo b.'); diff --git a/Tests/Generator/UrlGeneratorTest.php b/Tests/Generator/UrlGeneratorTest.php index d513b318..79caf33e 100644 --- a/Tests/Generator/UrlGeneratorTest.php +++ b/Tests/Generator/UrlGeneratorTest.php @@ -11,9 +11,10 @@ namespace Symfony\Component\Routing\Tests\Generator; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; -use Symfony\Bridge\PhpUnit\ExpectUserDeprecationMessageTrait; use Symfony\Component\Routing\Exception\InvalidParameterException; use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; use Symfony\Component\Routing\Exception\RouteCircularReferenceException; @@ -26,8 +27,6 @@ class UrlGeneratorTest extends TestCase { - use ExpectUserDeprecationMessageTrait; - public function testAbsoluteUrlWithPort80() { $routes = $this->getRoutes('test', new Route('/testing')); @@ -806,9 +805,8 @@ public function testAliasWhichTargetRouteDoesntExist() $this->getGenerator($routes)->generate('d'); } - /** - * @group legacy - */ + #[IgnoreDeprecations] + #[Group('legacy')] public function testDeprecatedAlias() { $this->expectUserDeprecationMessage('Since foo/bar 1.0.0: The "b" route alias is deprecated. You should stop using it, as it will be removed in the future.'); @@ -821,9 +819,8 @@ public function testDeprecatedAlias() $this->getGenerator($routes)->generate('b'); } - /** - * @group legacy - */ + #[IgnoreDeprecations] + #[Group('legacy')] public function testDeprecatedAliasWithCustomMessage() { $this->expectUserDeprecationMessage('Since foo/bar 1.0.0: foo b.'); @@ -836,9 +833,8 @@ public function testDeprecatedAliasWithCustomMessage() $this->getGenerator($routes)->generate('b'); } - /** - * @group legacy - */ + #[IgnoreDeprecations] + #[Group('legacy')] public function testTargettingADeprecatedAliasShouldTriggerDeprecation() { $this->expectUserDeprecationMessage('Since foo/bar 1.0.0: foo b.'); @@ -1109,9 +1105,8 @@ public function testQueryParameterCannotSubstituteRouteParameter() ]); } - /** - * @group legacy - */ + #[IgnoreDeprecations] + #[Group('legacy')] public function testQueryParametersWithScalarValue() { $routes = $this->getRoutes('user', new Route('/user/{id}')); diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 587ee4c0..6d89fd81 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,10 +1,11 @@ @@ -18,7 +19,7 @@ - + ./ @@ -26,5 +27,9 @@ ./Tests ./vendor - + + + + + From 22b0f6b1d4c97a58654d8326f58e76cc832869de Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 31 Jul 2025 14:36:46 +0200 Subject: [PATCH 12/25] replace PHPUnit annotations with attributes --- Tests/Attribute/RouteTest.php | 5 +- Tests/Generator/UrlGeneratorTest.php | 17 +- Tests/Loader/AttributeClassLoaderTest.php | 5 +- Tests/Loader/ContainerLoaderTest.php | 5 +- Tests/Loader/ObjectLoaderTest.php | 5 +- Tests/Loader/PhpFileLoaderTest.php | 5 +- Tests/Loader/Psr4DirectoryLoaderTest.php | 9 +- Tests/Loader/XmlFileLoaderTest.php | 17 +- Tests/Loader/YamlFileLoaderTest.php | 13 +- .../Dumper/CompiledUrlMatcherDumperTest.php | 5 +- .../Dumper/StaticPrefixCollectionTest.php | 5 +- .../ExpressionLanguageProviderTest.php | 9 +- Tests/RequestContextTest.php | 23 +- Tests/Requirement/EnumRequirementTest.php | 5 +- Tests/Requirement/RequirementTest.php | 412 +++++++----------- Tests/RouteCompilerTest.php | 21 +- Tests/RouteTest.php | 25 +- 17 files changed, 229 insertions(+), 357 deletions(-) diff --git a/Tests/Attribute/RouteTest.php b/Tests/Attribute/RouteTest.php index bbaa7563..8ff0a4dd 100644 --- a/Tests/Attribute/RouteTest.php +++ b/Tests/Attribute/RouteTest.php @@ -11,15 +11,14 @@ namespace Symfony\Component\Routing\Tests\Attribute; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\FooController; class RouteTest extends TestCase { - /** - * @dataProvider getValidParameters - */ + #[DataProvider('getValidParameters')] public function testLoadFromAttribute(string $methodName, string $getter, mixed $expectedReturn) { $route = (new \ReflectionMethod(FooController::class, $methodName))->getAttributes(Route::class)[0]->newInstance(); diff --git a/Tests/Generator/UrlGeneratorTest.php b/Tests/Generator/UrlGeneratorTest.php index 79caf33e..b6798cfe 100644 --- a/Tests/Generator/UrlGeneratorTest.php +++ b/Tests/Generator/UrlGeneratorTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Routing\Tests\Generator; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\IgnoreDeprecations; use PHPUnit\Framework\TestCase; @@ -109,9 +110,7 @@ public function testNotPassedOptionalParameterInBetween() $this->assertSame('/app.php/', $this->getGenerator($routes)->generate('test')); } - /** - * @dataProvider valuesProvider - */ + #[DataProvider('valuesProvider')] public function testRelativeUrlWithExtraParameters(string $expectedQueryString, string $parameter, $value) { $routes = $this->getRoutes('test', new Route('/testing')); @@ -120,9 +119,7 @@ public function testRelativeUrlWithExtraParameters(string $expectedQueryString, $this->assertSame('/app.php/testing'.$expectedQueryString, $url); } - /** - * @dataProvider valuesProvider - */ + #[DataProvider('valuesProvider')] public function testAbsoluteUrlWithExtraParameters(string $expectedQueryString, string $parameter, $value) { $routes = $this->getRoutes('test', new Route('/testing')); @@ -886,9 +883,7 @@ public function testIndirectCircularReferenceShouldThrowAnException() $this->getGenerator($routes)->generate('a'); } - /** - * @dataProvider provideRelativePaths - */ + #[DataProvider('provideRelativePaths')] public function testGetRelativePath($sourcePath, $targetPath, $expectedPath) { $this->assertSame($expectedPath, UrlGenerator::getRelativePath($sourcePath, $targetPath)); @@ -1027,9 +1022,7 @@ public function testFragmentsCanBeDefinedAsDefaults() $this->assertEquals('/app.php/testing#fragment', $url); } - /** - * @dataProvider provideLookAroundRequirementsInPath - */ + #[DataProvider('provideLookAroundRequirementsInPath')] public function testLookRoundRequirementsInPath($expected, $path, $requirement) { $routes = $this->getRoutes('test', new Route($path, [], ['foo' => $requirement, 'baz' => '.+?'])); diff --git a/Tests/Loader/AttributeClassLoaderTest.php b/Tests/Loader/AttributeClassLoaderTest.php index 50a10a16..2fa00e07 100644 --- a/Tests/Loader/AttributeClassLoaderTest.php +++ b/Tests/Loader/AttributeClassLoaderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Routing\Tests\Loader; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Routing\Alias; use Symfony\Component\Routing\Exception\LogicException; @@ -67,9 +68,7 @@ public function testGetResolver() $loader->getResolver(); } - /** - * @dataProvider provideTestSupportsChecksResource - */ + #[DataProvider('provideTestSupportsChecksResource')] public function testSupportsChecksResource($resource, $expectedSupports) { $this->assertSame($expectedSupports, $this->loader->supports($resource), '->supports() returns true if the resource is loadable'); diff --git a/Tests/Loader/ContainerLoaderTest.php b/Tests/Loader/ContainerLoaderTest.php index e4f99238..967a17b1 100644 --- a/Tests/Loader/ContainerLoaderTest.php +++ b/Tests/Loader/ContainerLoaderTest.php @@ -11,15 +11,14 @@ namespace Symfony\Component\Routing\Tests\Loader; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\Routing\Loader\ContainerLoader; class ContainerLoaderTest extends TestCase { - /** - * @dataProvider supportsProvider - */ + #[DataProvider('supportsProvider')] public function testSupports(bool $expected, ?string $type = null) { $this->assertSame($expected, (new ContainerLoader(new Container()))->supports('foo', $type)); diff --git a/Tests/Loader/ObjectLoaderTest.php b/Tests/Loader/ObjectLoaderTest.php index 42743fed..1b0d2673 100644 --- a/Tests/Loader/ObjectLoaderTest.php +++ b/Tests/Loader/ObjectLoaderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Routing\Tests\Loader; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Routing\Loader\ObjectLoader; use Symfony\Component\Routing\Route; @@ -40,9 +41,7 @@ public function testLoadCallsServiceAndReturnsCollection() $this->assertNotEmpty($actualRoutes->getResources()); } - /** - * @dataProvider getBadResourceStrings - */ + #[DataProvider('getBadResourceStrings')] public function testExceptionWithoutSyntax(string $resourceString) { $loader = new TestObjectLoader(); diff --git a/Tests/Loader/PhpFileLoaderTest.php b/Tests/Loader/PhpFileLoaderTest.php index 16071e5b..a52b61b6 100644 --- a/Tests/Loader/PhpFileLoaderTest.php +++ b/Tests/Loader/PhpFileLoaderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Routing\Tests\Loader; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderResolver; @@ -337,9 +338,7 @@ public function testImportingAliases() $this->assertEquals($expectedRoutes('php'), $routes); } - /** - * @dataProvider providePsr4ConfigFiles - */ + #[DataProvider('providePsr4ConfigFiles')] public function testImportAttributesWithPsr4Prefix(string $configFile) { $locator = new FileLocator(\dirname(__DIR__).'/Fixtures'); diff --git a/Tests/Loader/Psr4DirectoryLoaderTest.php b/Tests/Loader/Psr4DirectoryLoaderTest.php index 0720caca..9039ef9f 100644 --- a/Tests/Loader/Psr4DirectoryLoaderTest.php +++ b/Tests/Loader/Psr4DirectoryLoaderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Routing\Tests\Loader; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\DelegatingLoader; @@ -66,9 +67,7 @@ public function testAbstractController() $this->assertSame(MyChildController::class.'::someAction', $route->getDefault('_controller')); } - /** - * @dataProvider provideNamespacesThatNeedTrimming - */ + #[DataProvider('provideNamespacesThatNeedTrimming')] public function testPsr4NamespaceTrim(string $namespace) { $route = $this->getLoader() @@ -91,9 +90,7 @@ public static function provideNamespacesThatNeedTrimming(): array ]; } - /** - * @dataProvider provideInvalidPsr4Namespaces - */ + #[DataProvider('provideInvalidPsr4Namespaces')] public function testInvalidPsr4Namespace(string $namespace, string $expectedExceptionMessage) { $this->expectException(InvalidArgumentException::class); diff --git a/Tests/Loader/XmlFileLoaderTest.php b/Tests/Loader/XmlFileLoaderTest.php index 7afc3d2e..e8fe104d 100644 --- a/Tests/Loader/XmlFileLoaderTest.php +++ b/Tests/Loader/XmlFileLoaderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Routing\Tests\Loader; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderResolver; @@ -214,9 +215,7 @@ public function testLocalizedImportsOfNotLocalizedRoutes() $this->assertSame('en', $routeCollection->get('imported.en')->getRequirement('_locale')); } - /** - * @dataProvider getPathsToInvalidFiles - */ + #[DataProvider('getPathsToInvalidFiles')] public function testLoadThrowsExceptionWithInvalidFile($filePath) { $loader = new XmlFileLoader(new FileLocator([__DIR__.'/../Fixtures'])); @@ -226,9 +225,7 @@ public function testLoadThrowsExceptionWithInvalidFile($filePath) $loader->load($filePath); } - /** - * @dataProvider getPathsToInvalidFiles - */ + #[DataProvider('getPathsToInvalidFiles')] public function testLoadThrowsExceptionWithInvalidFileEvenWithoutSchemaValidation(string $filePath) { $loader = new CustomXmlFileLoader(new FileLocator([__DIR__.'/../Fixtures'])); @@ -472,9 +469,7 @@ public function testOverrideControllerInDefaults() $loader->load('override_defaults.xml'); } - /** - * @dataProvider provideFilesImportingRoutesWithControllers - */ + #[DataProvider('provideFilesImportingRoutesWithControllers')] public function testImportRouteWithController(string $file) { $loader = new XmlFileLoader(new FileLocator([__DIR__.'/../Fixtures/controller'])); @@ -617,9 +612,7 @@ public function testImportingAliases() $this->assertEquals($expectedRoutes('xml'), $routes); } - /** - * @dataProvider providePsr4ConfigFiles - */ + #[DataProvider('providePsr4ConfigFiles')] public function testImportAttributesWithPsr4Prefix(string $configFile) { $locator = new FileLocator(\dirname(__DIR__).'/Fixtures'); diff --git a/Tests/Loader/YamlFileLoaderTest.php b/Tests/Loader/YamlFileLoaderTest.php index 4f6ed3a2..beb15ae3 100644 --- a/Tests/Loader/YamlFileLoaderTest.php +++ b/Tests/Loader/YamlFileLoaderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Routing\Tests\Loader; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderResolver; @@ -46,9 +47,7 @@ public function testLoadDoesNothingIfEmpty() $this->assertEquals([new FileResource(realpath(__DIR__.'/../Fixtures/empty.yml'))], $collection->getResources()); } - /** - * @dataProvider getPathsToInvalidFiles - */ + #[DataProvider('getPathsToInvalidFiles')] public function testLoadThrowsExceptionWithInvalidFile(string $filePath) { $loader = new YamlFileLoader(new FileLocator([__DIR__.'/../Fixtures'])); @@ -161,9 +160,7 @@ public function testOverrideControllerInDefaults() $loader->load('override_defaults.yml'); } - /** - * @dataProvider provideFilesImportingRoutesWithControllers - */ + #[DataProvider('provideFilesImportingRoutesWithControllers')] public function testImportRouteWithController($file) { $loader = new YamlFileLoader(new FileLocator([__DIR__.'/../Fixtures/controller'])); @@ -522,9 +519,7 @@ protected function configureRoute( $this->assertSame(1, $routes->getPriority('also_important')); } - /** - * @dataProvider providePsr4ConfigFiles - */ + #[DataProvider('providePsr4ConfigFiles')] public function testImportAttributesWithPsr4Prefix(string $configFile) { $locator = new FileLocator(\dirname(__DIR__).'/Fixtures'); diff --git a/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php b/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php index 1bb2c2e3..cca2c365 100644 --- a/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php +++ b/Tests/Matcher/Dumper/CompiledUrlMatcherDumperTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Routing\Tests\Matcher\Dumper; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Config\FileLocator; use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; @@ -47,9 +48,7 @@ public function testRedirectPreservesUrlEncoding() $matcher->match('/foo%3Abar'); } - /** - * @dataProvider getRouteCollections - */ + #[DataProvider('getRouteCollections')] public function testDump(RouteCollection $collection, $fixture) { $basePath = __DIR__.'/../../Fixtures/dumper/'; diff --git a/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php b/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php index 9935ced4..f88fcdba 100644 --- a/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php +++ b/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php @@ -11,15 +11,14 @@ namespace Symfony\Component\Routing\Tests\Matcher\Dumper; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Routing\Matcher\Dumper\StaticPrefixCollection; use Symfony\Component\Routing\Route; class StaticPrefixCollectionTest extends TestCase { - /** - * @dataProvider routeProvider - */ + #[DataProvider('routeProvider')] public function testGrouping(array $routes, $expected) { $collection = new StaticPrefixCollection('/'); diff --git a/Tests/Matcher/ExpressionLanguageProviderTest.php b/Tests/Matcher/ExpressionLanguageProviderTest.php index 71280257..2e5ab7ca 100644 --- a/Tests/Matcher/ExpressionLanguageProviderTest.php +++ b/Tests/Matcher/ExpressionLanguageProviderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Routing\Tests\Matcher; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; @@ -40,9 +41,7 @@ protected function setUp(): void $this->expressionLanguage->registerProvider(new ExpressionLanguageProvider($functionProvider)); } - /** - * @dataProvider compileProvider - */ + #[DataProvider('compileProvider')] public function testCompile(string $expression, string $expected) { $this->assertSame($expected, $this->expressionLanguage->compile($expression)); @@ -57,9 +56,7 @@ public static function compileProvider(): iterable ]; } - /** - * @dataProvider evaluateProvider - */ + #[DataProvider('evaluateProvider')] public function testEvaluate(string $expression, $expected) { $this->assertSame($expected, $this->expressionLanguage->evaluate($expression, ['context' => $this->context])); diff --git a/Tests/RequestContextTest.php b/Tests/RequestContextTest.php index fcc42ff5..d815fbed 100644 --- a/Tests/RequestContextTest.php +++ b/Tests/RequestContextTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Routing\Tests; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\RequestContext; @@ -85,18 +86,16 @@ public function testFromUriBeingEmpty() $this->assertSame('/', $requestContext->getPathInfo()); } - /** - * @testWith ["http://foo.com\\bar"] - * ["\\\\foo.com/bar"] - * ["a\rb"] - * ["a\nb"] - * ["a\tb"] - * ["\u0000foo"] - * ["foo\u0000"] - * [" foo"] - * ["foo "] - * [":"] - */ + #[TestWith(['http://foo.com\\bar'])] + #[TestWith(['\\\\foo.com/bar'])] + #[TestWith(["a\rb"])] + #[TestWith(["a\nb"])] + #[TestWith(["a\tb"])] + #[TestWith(["\u0000foo"])] + #[TestWith(["foo\u0000"])] + #[TestWith([' foo'])] + #[TestWith(['foo '])] + #[TestWith([':'])] public function testFromBadUri(string $uri) { $context = RequestContext::fromUri($uri); diff --git a/Tests/Requirement/EnumRequirementTest.php b/Tests/Requirement/EnumRequirementTest.php index 68b32ea7..842d4baa 100644 --- a/Tests/Requirement/EnumRequirementTest.php +++ b/Tests/Requirement/EnumRequirementTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Routing\Tests\Requirement; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Routing\Exception\InvalidArgumentException; use Symfony\Component\Routing\Requirement\EnumRequirement; @@ -46,9 +47,7 @@ public function testCaseFromAnotherEnum() new EnumRequirement([TestStringBackedEnum::Diamonds, TestStringBackedEnum2::Spades]); } - /** - * @dataProvider provideToString - */ + #[DataProvider('provideToString')] public function testToString(string $expected, string|array $cases = []) { $this->assertSame($expected, (string) new EnumRequirement($cases)); diff --git a/Tests/Requirement/RequirementTest.php b/Tests/Requirement/RequirementTest.php index d7e0ba07..24c58630 100644 --- a/Tests/Requirement/RequirementTest.php +++ b/Tests/Requirement/RequirementTest.php @@ -11,21 +11,21 @@ namespace Symfony\Component\Routing\Tests\Requirement; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use Symfony\Component\Routing\Requirement\Requirement; use Symfony\Component\Routing\Route; class RequirementTest extends TestCase { - /** - * @testWith ["FOO"] - * ["foo"] - * ["1987"] - * ["42-42"] - * ["fo2o-bar"] - * ["foo-bA198r-Ccc"] - * ["fo10O-bar-CCc-fooba187rccc"] - */ + #[TestWith(['FOO'])] + #[TestWith(['foo'])] + #[TestWith(['1987'])] + #[TestWith(['42-42'])] + #[TestWith(['fo2o-bar'])] + #[TestWith(['foo-bA198r-Ccc'])] + #[TestWith(['fo10O-bar-CCc-fooba187rccc'])] public function testAsciiSlugOK(string $slug) { $this->assertMatchesRegularExpression( @@ -34,16 +34,14 @@ public function testAsciiSlugOK(string $slug) ); } - /** - * @testWith [""] - * ["-"] - * ["fôo"] - * ["-FOO"] - * ["foo-"] - * ["-foo-"] - * ["-foo-bar-"] - * ["foo--bar"] - */ + #[TestWith([''])] + #[TestWith(['-'])] + #[TestWith(['fôo'])] + #[TestWith(['-FOO'])] + #[TestWith(['foo-'])] + #[TestWith(['-foo-'])] + #[TestWith(['-foo-bar-'])] + #[TestWith(['foo--bar'])] public function testAsciiSlugKO(string $slug) { $this->assertDoesNotMatchRegularExpression( @@ -52,11 +50,9 @@ public function testAsciiSlugKO(string $slug) ); } - /** - * @testWith ["foo"] - * ["foo/bar/ccc"] - * ["///"] - */ + #[TestWith(['foo'])] + #[TestWith(['foo/bar/ccc'])] + #[TestWith(['///'])] public function testCatchAllOK(string $path) { $this->assertMatchesRegularExpression( @@ -65,9 +61,7 @@ public function testCatchAllOK(string $path) ); } - /** - * @testWith [""] - */ + #[TestWith([''])] public function testCatchAllKO(string $path) { $this->assertDoesNotMatchRegularExpression( @@ -76,13 +70,11 @@ public function testCatchAllKO(string $path) ); } - /** - * @testWith ["0000-01-01"] - * ["9999-12-31"] - * ["2022-04-15"] - * ["2024-02-29"] - * ["1243-04-31"] - */ + #[TestWith(['0000-01-01'])] + #[TestWith(['9999-12-31'])] + #[TestWith(['2022-04-15'])] + #[TestWith(['2024-02-29'])] + #[TestWith(['1243-04-31'])] public function testDateYmdOK(string $date) { $this->assertMatchesRegularExpression( @@ -91,14 +83,12 @@ public function testDateYmdOK(string $date) ); } - /** - * @testWith [""] - * ["foo"] - * ["0000-01-00"] - * ["9999-00-31"] - * ["2022-02-30"] - * ["2022-02-31"] - */ + #[TestWith([''])] + #[TestWith(['foo'])] + #[TestWith(['0000-01-00'])] + #[TestWith(['9999-00-31'])] + #[TestWith(['2022-02-30'])] + #[TestWith(['2022-02-31'])] public function testDateYmdKO(string $date) { $this->assertDoesNotMatchRegularExpression( @@ -107,14 +97,12 @@ public function testDateYmdKO(string $date) ); } - /** - * @testWith ["0"] - * ["012"] - * ["1"] - * ["42"] - * ["42198"] - * ["999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"] - */ + #[TestWith(['0'])] + #[TestWith(['012'])] + #[TestWith(['1'])] + #[TestWith(['42'])] + #[TestWith(['42198'])] + #[TestWith(['999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999'])] public function testDigitsOK(string $digits) { $this->assertMatchesRegularExpression( @@ -123,12 +111,10 @@ public function testDigitsOK(string $digits) ); } - /** - * @testWith [""] - * ["foo"] - * ["-1"] - * ["3.14"] - */ + #[TestWith([''])] + #[TestWith(['foo'])] + #[TestWith(['-1'])] + #[TestWith(['3.14'])] public function testDigitsKO(string $digits) { $this->assertDoesNotMatchRegularExpression( @@ -137,10 +123,8 @@ public function testDigitsKO(string $digits) ); } - /** - * @testWith ["67c8b7d295c70befc3070bf2"] - * ["000000000000000000000000"] - */ + #[TestWith(['67c8b7d295c70befc3070bf2'])] + #[TestWith(['000000000000000000000000'])] public function testMongoDbIdOK(string $id) { $this->assertMatchesRegularExpression( @@ -149,12 +133,10 @@ public function testMongoDbIdOK(string $id) ); } - /** - * @testWith ["67C8b7D295C70BEFC3070BF2"] - * ["67c8b7d295c70befc3070bg2"] - * ["67c8b7d295c70befc3070bf2a"] - * ["67c8b7d295c70befc3070bf"] - */ + #[TestWith(['67C8b7D295C70BEFC3070BF2'])] + #[TestWith(['67c8b7d295c70befc3070bg2'])] + #[TestWith(['67c8b7d295c70befc3070bf2a'])] + #[TestWith(['67c8b7d295c70befc3070bf'])] public function testMongoDbIdKO(string $id) { $this->assertDoesNotMatchRegularExpression( @@ -163,12 +145,10 @@ public function testMongoDbIdKO(string $id) ); } - /** - * @testWith ["1"] - * ["42"] - * ["42198"] - * ["999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"] - */ + #[TestWith(['1'])] + #[TestWith(['42'])] + #[TestWith(['42198'])] + #[TestWith(['999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999'])] public function testPositiveIntOK(string $digits) { $this->assertMatchesRegularExpression( @@ -177,14 +157,12 @@ public function testPositiveIntOK(string $digits) ); } - /** - * @testWith [""] - * ["0"] - * ["045"] - * ["foo"] - * ["-1"] - * ["3.14"] - */ + #[TestWith([''])] + #[TestWith(['0'])] + #[TestWith(['045'])] + #[TestWith(['foo'])] + #[TestWith(['-1'])] + #[TestWith(['3.14'])] public function testPositiveIntKO(string $digits) { $this->assertDoesNotMatchRegularExpression( @@ -193,12 +171,10 @@ public function testPositiveIntKO(string $digits) ); } - /** - * @testWith ["00000000000000000000000000"] - * ["ZZZZZZZZZZZZZZZZZZZZZZZZZZ"] - * ["01G0P4XH09KW3RCF7G4Q57ESN0"] - * ["05CSACM1MS9RB9H5F61BYA146Q"] - */ + #[TestWith(['00000000000000000000000000'])] + #[TestWith(['ZZZZZZZZZZZZZZZZZZZZZZZZZZ'])] + #[TestWith(['01G0P4XH09KW3RCF7G4Q57ESN0'])] + #[TestWith(['05CSACM1MS9RB9H5F61BYA146Q'])] public function testUidBase32OK(string $uid) { $this->assertMatchesRegularExpression( @@ -207,12 +183,10 @@ public function testUidBase32OK(string $uid) ); } - /** - * @testWith [""] - * ["foo"] - * ["01G0P4XH09KW3RCF7G4Q57ESN"] - * ["01G0P4XH09KW3RCF7G4Q57ESNU"] - */ + #[TestWith([''])] + #[TestWith(['foo'])] + #[TestWith(['01G0P4XH09KW3RCF7G4Q57ESN'])] + #[TestWith(['01G0P4XH09KW3RCF7G4Q57ESNU'])] public function testUidBase32KO(string $uid) { $this->assertDoesNotMatchRegularExpression( @@ -221,12 +195,10 @@ public function testUidBase32KO(string $uid) ); } - /** - * @testWith ["1111111111111111111111"] - * ["zzzzzzzzzzzzzzzzzzzzzz"] - * ["1BkPBX6T19U8TUAjBTtgwH"] - * ["1fg491dt8eQpf2TU42o2bY"] - */ + #[TestWith(['1111111111111111111111'])] + #[TestWith(['zzzzzzzzzzzzzzzzzzzzzz'])] + #[TestWith(['1BkPBX6T19U8TUAjBTtgwH'])] + #[TestWith(['1fg491dt8eQpf2TU42o2bY'])] public function testUidBase58OK(string $uid) { $this->assertMatchesRegularExpression( @@ -235,12 +207,10 @@ public function testUidBase58OK(string $uid) ); } - /** - * @testWith [""] - * ["foo"] - * ["1BkPBX6T19U8TUAjBTtgw"] - * ["1BkPBX6T19U8TUAjBTtgwI"] - */ + #[TestWith([''])] + #[TestWith(['foo'])] + #[TestWith(['1BkPBX6T19U8TUAjBTtgw'])] + #[TestWith(['1BkPBX6T19U8TUAjBTtgwI'])] public function testUidBase58KO(string $uid) { $this->assertDoesNotMatchRegularExpression( @@ -249,9 +219,7 @@ public function testUidBase58KO(string $uid) ); } - /** - * @dataProvider provideUidRfc4122 - */ + #[DataProvider('provideUidRfc4122')] public function testUidRfc4122OK(string $uid) { $this->assertMatchesRegularExpression( @@ -260,9 +228,7 @@ public function testUidRfc4122OK(string $uid) ); } - /** - * @dataProvider provideUidRfc4122KO - */ + #[DataProvider('provideUidRfc4122KO')] public function testUidRfc4122KO(string $uid) { $this->assertDoesNotMatchRegularExpression( @@ -271,9 +237,7 @@ public function testUidRfc4122KO(string $uid) ); } - /** - * @dataProvider provideUidRfc4122 - */ + #[DataProvider('provideUidRfc4122')] public function testUidRfc9562OK(string $uid) { $this->assertMatchesRegularExpression( @@ -282,9 +246,7 @@ public function testUidRfc9562OK(string $uid) ); } - /** - * @dataProvider provideUidRfc4122KO - */ + #[DataProvider('provideUidRfc4122KO')] public function testUidRfc9562KO(string $uid) { $this->assertDoesNotMatchRegularExpression( @@ -310,11 +272,9 @@ public static function provideUidRfc4122KO(): iterable yield ['01802c4ec4099f07863cf025ca7766a0']; } - /** - * @testWith ["00000000000000000000000000"] - * ["7ZZZZZZZZZZZZZZZZZZZZZZZZZ"] - * ["01G0P4ZPM69QTD4MM4ENAEA4EW"] - */ + #[TestWith(['00000000000000000000000000'])] + #[TestWith(['7ZZZZZZZZZZZZZZZZZZZZZZZZZ'])] + #[TestWith(['01G0P4ZPM69QTD4MM4ENAEA4EW'])] public function testUlidOK(string $ulid) { $this->assertMatchesRegularExpression( @@ -323,12 +283,10 @@ public function testUlidOK(string $ulid) ); } - /** - * @testWith [""] - * ["foo"] - * ["8ZZZZZZZZZZZZZZZZZZZZZZZZZ"] - * ["01G0P4ZPM69QTD4MM4ENAEA4E"] - */ + #[TestWith([''])] + #[TestWith(['foo'])] + #[TestWith(['8ZZZZZZZZZZZZZZZZZZZZZZZZZ'])] + #[TestWith(['01G0P4ZPM69QTD4MM4ENAEA4E'])] public function testUlidKO(string $ulid) { $this->assertDoesNotMatchRegularExpression( @@ -337,15 +295,13 @@ public function testUlidKO(string $ulid) ); } - /** - * @testWith ["00000000-0000-1000-8000-000000000000"] - * ["ffffffff-ffff-6fff-bfff-ffffffffffff"] - * ["8c670a1c-bc95-11ec-8422-0242ac120002"] - * ["61c86569-e477-3ed9-9e3b-1562edb03277"] - * ["e55a29be-ba25-46e0-a5e5-85b78a6f9a11"] - * ["bad98960-f1a1-530e-9a82-07d0b6c4e62f"] - * ["1ecbc9a8-432d-6b14-af93-715adc3b830c"] - */ + #[TestWith(['00000000-0000-1000-8000-000000000000'])] + #[TestWith(['ffffffff-ffff-6fff-bfff-ffffffffffff'])] + #[TestWith(['8c670a1c-bc95-11ec-8422-0242ac120002'])] + #[TestWith(['61c86569-e477-3ed9-9e3b-1562edb03277'])] + #[TestWith(['e55a29be-ba25-46e0-a5e5-85b78a6f9a11'])] + #[TestWith(['bad98960-f1a1-530e-9a82-07d0b6c4e62f'])] + #[TestWith(['1ecbc9a8-432d-6b14-af93-715adc3b830c'])] public function testUuidOK(string $uuid) { $this->assertMatchesRegularExpression( @@ -354,15 +310,13 @@ public function testUuidOK(string $uuid) ); } - /** - * @testWith [""] - * ["foo"] - * ["01802c74-d78c-b085-0cdf-7cbad87c70a3"] - * ["e55a29be-ba25-46e0-a5e5-85b78a6f9a1"] - * ["e55a29bh-ba25-46e0-a5e5-85b78a6f9a11"] - * ["e55a29beba2546e0a5e585b78a6f9a11"] - * ["21902510-bc96-21ec-8422-0242ac120002"] - */ + #[TestWith([''])] + #[TestWith(['foo'])] + #[TestWith(['01802c74-d78c-b085-0cdf-7cbad87c70a3'])] + #[TestWith(['e55a29be-ba25-46e0-a5e5-85b78a6f9a1'])] + #[TestWith(['e55a29bh-ba25-46e0-a5e5-85b78a6f9a11'])] + #[TestWith(['e55a29beba2546e0a5e585b78a6f9a11'])] + #[TestWith(['21902510-bc96-21ec-8422-0242ac120002'])] public function testUuidKO(string $uuid) { $this->assertDoesNotMatchRegularExpression( @@ -371,13 +325,11 @@ public function testUuidKO(string $uuid) ); } - /** - * @testWith ["00000000-0000-1000-8000-000000000000"] - * ["ffffffff-ffff-1fff-bfff-ffffffffffff"] - * ["21902510-bc96-11ec-8422-0242ac120002"] - * ["a8ff8f60-088e-1099-a09d-53afc49918d1"] - * ["b0ac612c-9117-17a1-901f-53afc49918d1"] - */ + #[TestWith(['00000000-0000-1000-8000-000000000000'])] + #[TestWith(['ffffffff-ffff-1fff-bfff-ffffffffffff'])] + #[TestWith(['21902510-bc96-11ec-8422-0242ac120002'])] + #[TestWith(['a8ff8f60-088e-1099-a09d-53afc49918d1'])] + #[TestWith(['b0ac612c-9117-17a1-901f-53afc49918d1'])] public function testUuidV1OK(string $uuid) { $this->assertMatchesRegularExpression( @@ -386,14 +338,12 @@ public function testUuidV1OK(string $uuid) ); } - /** - * @testWith [""] - * ["foo"] - * ["a3674b89-0170-3e30-8689-52939013e39c"] - * ["e0040090-3cb0-4bf9-a868-407770c964f9"] - * ["2e2b41d9-e08c-53d2-b435-818b9c323942"] - * ["2a37b67a-5eaa-6424-b5d6-ffc9ba0f2a13"] - */ + #[TestWith([''])] + #[TestWith(['foo'])] + #[TestWith(['a3674b89-0170-3e30-8689-52939013e39c'])] + #[TestWith(['e0040090-3cb0-4bf9-a868-407770c964f9'])] + #[TestWith(['2e2b41d9-e08c-53d2-b435-818b9c323942'])] + #[TestWith(['2a37b67a-5eaa-6424-b5d6-ffc9ba0f2a13'])] public function testUuidV1KO(string $uuid) { $this->assertDoesNotMatchRegularExpression( @@ -402,12 +352,10 @@ public function testUuidV1KO(string $uuid) ); } - /** - * @testWith ["00000000-0000-3000-8000-000000000000"] - * ["ffffffff-ffff-3fff-bfff-ffffffffffff"] - * ["2b3f1427-33b2-30a9-8759-07355007c204"] - * ["c38e7b09-07f7-3901-843d-970b0186b873"] - */ + #[TestWith(['00000000-0000-3000-8000-000000000000'])] + #[TestWith(['ffffffff-ffff-3fff-bfff-ffffffffffff'])] + #[TestWith(['2b3f1427-33b2-30a9-8759-07355007c204'])] + #[TestWith(['c38e7b09-07f7-3901-843d-970b0186b873'])] public function testUuidV3OK(string $uuid) { $this->assertMatchesRegularExpression( @@ -416,14 +364,12 @@ public function testUuidV3OK(string $uuid) ); } - /** - * @testWith [""] - * ["foo"] - * ["e24d9c0e-bc98-11ec-9924-53afc49918d1"] - * ["1c240248-7d0b-41a4-9d20-61ad2915a58c"] - * ["4816b668-385b-5a65-808d-bca410f45090"] - * ["1d2f3104-dff6-64c6-92ff-0f74b1d0e2af"] - */ + #[TestWith([''])] + #[TestWith(['foo'])] + #[TestWith(['e24d9c0e-bc98-11ec-9924-53afc49918d1'])] + #[TestWith(['1c240248-7d0b-41a4-9d20-61ad2915a58c'])] + #[TestWith(['4816b668-385b-5a65-808d-bca410f45090'])] + #[TestWith(['1d2f3104-dff6-64c6-92ff-0f74b1d0e2af'])] public function testUuidV3KO(string $uuid) { $this->assertDoesNotMatchRegularExpression( @@ -432,12 +378,10 @@ public function testUuidV3KO(string $uuid) ); } - /** - * @testWith ["00000000-0000-4000-8000-000000000000"] - * ["ffffffff-ffff-4fff-bfff-ffffffffffff"] - * ["b8f15bf4-46e2-4757-bbce-11ae83f7a6ea"] - * ["eaf51230-1ce2-40f1-ab18-649212b26198"] - */ + #[TestWith(['00000000-0000-4000-8000-000000000000'])] + #[TestWith(['ffffffff-ffff-4fff-bfff-ffffffffffff'])] + #[TestWith(['b8f15bf4-46e2-4757-bbce-11ae83f7a6ea'])] + #[TestWith(['eaf51230-1ce2-40f1-ab18-649212b26198'])] public function testUuidV4OK(string $uuid) { $this->assertMatchesRegularExpression( @@ -446,14 +390,12 @@ public function testUuidV4OK(string $uuid) ); } - /** - * @testWith [""] - * ["foo"] - * ["15baaab2-f310-11d2-9ecf-53afc49918d1"] - * ["acd44dc8-d2cc-326c-9e3a-80a3305a25e8"] - * ["7fc2705f-a8a4-5b31-99a8-890686d64189"] - * ["1ecbc991-3552-6920-998e-efad54178a98"] - */ + #[TestWith([''])] + #[TestWith(['foo'])] + #[TestWith(['15baaab2-f310-11d2-9ecf-53afc49918d1'])] + #[TestWith(['acd44dc8-d2cc-326c-9e3a-80a3305a25e8'])] + #[TestWith(['7fc2705f-a8a4-5b31-99a8-890686d64189'])] + #[TestWith(['1ecbc991-3552-6920-998e-efad54178a98'])] public function testUuidV4KO(string $uuid) { $this->assertDoesNotMatchRegularExpression( @@ -462,12 +404,10 @@ public function testUuidV4KO(string $uuid) ); } - /** - * @testWith ["00000000-0000-5000-8000-000000000000"] - * ["ffffffff-ffff-5fff-bfff-ffffffffffff"] - * ["49f4d32c-28b3-5802-8717-a2896180efbd"] - * ["58b3c62e-a7df-5a82-93a6-fbe5fda681c1"] - */ + #[TestWith(['00000000-0000-5000-8000-000000000000'])] + #[TestWith(['ffffffff-ffff-5fff-bfff-ffffffffffff'])] + #[TestWith(['49f4d32c-28b3-5802-8717-a2896180efbd'])] + #[TestWith(['58b3c62e-a7df-5a82-93a6-fbe5fda681c1'])] public function testUuidV5OK(string $uuid) { $this->assertMatchesRegularExpression( @@ -476,14 +416,12 @@ public function testUuidV5OK(string $uuid) ); } - /** - * @testWith [""] - * ["foo"] - * ["b99ad578-fdd3-1135-9d3b-53afc49918d1"] - * ["b3ee3071-7a2b-3e17-afdf-6b6aec3acf85"] - * ["2ab4f5a7-6412-46c1-b3ab-1fe1ed391e27"] - * ["135fdd3d-e193-653e-865d-67e88cf12e44"] - */ + #[TestWith([''])] + #[TestWith(['foo'])] + #[TestWith(['b99ad578-fdd3-1135-9d3b-53afc49918d1'])] + #[TestWith(['b3ee3071-7a2b-3e17-afdf-6b6aec3acf85'])] + #[TestWith(['2ab4f5a7-6412-46c1-b3ab-1fe1ed391e27'])] + #[TestWith(['135fdd3d-e193-653e-865d-67e88cf12e44'])] public function testUuidV5KO(string $uuid) { $this->assertDoesNotMatchRegularExpression( @@ -492,13 +430,11 @@ public function testUuidV5KO(string $uuid) ); } - /** - * @testWith ["00000000-0000-6000-8000-000000000000"] - * ["ffffffff-ffff-6fff-bfff-ffffffffffff"] - * ["2c51caad-c72f-66b2-b6d7-8766d36c73df"] - * ["17941ebb-48fa-6bfe-9bbd-43929f8784f5"] - * ["1ecbc993-f6c2-67f2-8fbe-295ed594b344"] - */ + #[TestWith(['00000000-0000-6000-8000-000000000000'])] + #[TestWith(['ffffffff-ffff-6fff-bfff-ffffffffffff'])] + #[TestWith(['2c51caad-c72f-66b2-b6d7-8766d36c73df'])] + #[TestWith(['17941ebb-48fa-6bfe-9bbd-43929f8784f5'])] + #[TestWith(['1ecbc993-f6c2-67f2-8fbe-295ed594b344'])] public function testUuidV6OK(string $uuid) { $this->assertMatchesRegularExpression( @@ -507,14 +443,12 @@ public function testUuidV6OK(string $uuid) ); } - /** - * @testWith [""] - * ["foo"] - * ["821040f4-7b67-12a3-9770-53afc49918d1"] - * ["802dc245-aaaa-3649-98c6-31c549b0df86"] - * ["92d2e5ad-bc4e-4947-a8d9-77706172ca83"] - * ["6e124559-d260-511e-afdc-e57c7025fed0"] - */ + #[TestWith([''])] + #[TestWith(['foo'])] + #[TestWith(['821040f4-7b67-12a3-9770-53afc49918d1'])] + #[TestWith(['802dc245-aaaa-3649-98c6-31c549b0df86'])] + #[TestWith(['92d2e5ad-bc4e-4947-a8d9-77706172ca83'])] + #[TestWith(['6e124559-d260-511e-afdc-e57c7025fed0'])] public function testUuidV6KO(string $uuid) { $this->assertDoesNotMatchRegularExpression( @@ -523,11 +457,9 @@ public function testUuidV6KO(string $uuid) ); } - /** - * @testWith ["00000000-0000-7000-8000-000000000000"] - * ["ffffffff-ffff-7fff-bfff-ffffffffffff"] - * ["01910577-4898-7c47-966e-68d127dde2ac"] - */ + #[TestWith(['00000000-0000-7000-8000-000000000000'])] + #[TestWith(['ffffffff-ffff-7fff-bfff-ffffffffffff'])] + #[TestWith(['01910577-4898-7c47-966e-68d127dde2ac'])] public function testUuidV7OK(string $uuid) { $this->assertMatchesRegularExpression( @@ -536,14 +468,12 @@ public function testUuidV7OK(string $uuid) ); } - /** - * @testWith [""] - * ["foo"] - * ["15baaab2-f310-11d2-9ecf-53afc49918d1"] - * ["acd44dc8-d2cc-326c-9e3a-80a3305a25e8"] - * ["7fc2705f-a8a4-5b31-99a8-890686d64189"] - * ["1ecbc991-3552-6920-998e-efad54178a98"] - */ + #[TestWith([''])] + #[TestWith(['foo'])] + #[TestWith(['15baaab2-f310-11d2-9ecf-53afc49918d1'])] + #[TestWith(['acd44dc8-d2cc-326c-9e3a-80a3305a25e8'])] + #[TestWith(['7fc2705f-a8a4-5b31-99a8-890686d64189'])] + #[TestWith(['1ecbc991-3552-6920-998e-efad54178a98'])] public function testUuidV7KO(string $uuid) { $this->assertDoesNotMatchRegularExpression( @@ -552,11 +482,9 @@ public function testUuidV7KO(string $uuid) ); } - /** - * @testWith ["00000000-0000-8000-8000-000000000000"] - * ["ffffffff-ffff-8fff-bfff-ffffffffffff"] - * ["01910577-4898-8c47-966e-68d127dde2ac"] - */ + #[TestWith(['00000000-0000-8000-8000-000000000000'])] + #[TestWith(['ffffffff-ffff-8fff-bfff-ffffffffffff'])] + #[TestWith(['01910577-4898-8c47-966e-68d127dde2ac'])] public function testUuidV8OK(string $uuid) { $this->assertMatchesRegularExpression( @@ -565,14 +493,12 @@ public function testUuidV8OK(string $uuid) ); } - /** - * @testWith [""] - * ["foo"] - * ["15baaab2-f310-11d2-9ecf-53afc49918d1"] - * ["acd44dc8-d2cc-326c-9e3a-80a3305a25e8"] - * ["7fc2705f-a8a4-5b31-99a8-890686d64189"] - * ["1ecbc991-3552-6920-998e-efad54178a98"] - */ + #[TestWith([''])] + #[TestWith(['foo'])] + #[TestWith(['15baaab2-f310-11d2-9ecf-53afc49918d1'])] + #[TestWith(['acd44dc8-d2cc-326c-9e3a-80a3305a25e8'])] + #[TestWith(['7fc2705f-a8a4-5b31-99a8-890686d64189'])] + #[TestWith(['1ecbc991-3552-6920-998e-efad54178a98'])] public function testUuidV8KO(string $uuid) { $this->assertDoesNotMatchRegularExpression( diff --git a/Tests/RouteCompilerTest.php b/Tests/RouteCompilerTest.php index 0a756593..af8941d6 100644 --- a/Tests/RouteCompilerTest.php +++ b/Tests/RouteCompilerTest.php @@ -11,15 +11,14 @@ namespace Symfony\Component\Routing\Tests; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCompiler; class RouteCompilerTest extends TestCase { - /** - * @dataProvider provideCompileData - */ + #[DataProvider('provideCompileData')] public function testCompile($name, $arguments, $prefix, $regex, $variables, $tokens) { $r = new \ReflectionClass(Route::class); @@ -183,9 +182,7 @@ public static function provideCompileData() ]; } - /** - * @dataProvider provideCompileImplicitUtf8Data - */ + #[DataProvider('provideCompileImplicitUtf8Data')] public function testCompileImplicitUtf8Data($name, $arguments, $prefix, $regex, $variables, $tokens) { $this->expectException(\LogicException::class); @@ -277,9 +274,7 @@ public function testRouteWithFragmentAsPathParameter() $route->compile(); } - /** - * @dataProvider getVariableNamesStartingWithADigit - */ + #[DataProvider('getVariableNamesStartingWithADigit')] public function testRouteWithVariableNameStartingWithADigit(string $name) { $this->expectException(\DomainException::class); @@ -296,9 +291,7 @@ public static function getVariableNamesStartingWithADigit() ]; } - /** - * @dataProvider provideCompileWithHostData - */ + #[DataProvider('provideCompileWithHostData')] public function testCompileWithHost(string $name, array $arguments, string $prefix, string $regex, array $variables, array $pathVariables, array $tokens, string $hostRegex, array $hostVariables, array $hostTokens) { $r = new \ReflectionClass(Route::class); @@ -376,9 +369,7 @@ public function testRouteWithTooLongVariableName() $route->compile(); } - /** - * @dataProvider provideRemoveCapturingGroup - */ + #[DataProvider('provideRemoveCapturingGroup')] public function testRemoveCapturingGroup(string $regex, string $requirement) { $route = new Route('/{foo}', [], ['foo' => $requirement]); diff --git a/Tests/RouteTest.php b/Tests/RouteTest.php index 34728042..cc67c18c 100644 --- a/Tests/RouteTest.php +++ b/Tests/RouteTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Routing\Tests; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\Routing\CompiledRoute; use Symfony\Component\Routing\Route; @@ -141,9 +142,7 @@ public function testRequirementAlternativeStartAndEndRegexSyntax() $this->assertTrue($route->hasRequirement('foo')); } - /** - * @dataProvider getInvalidRequirements - */ + #[DataProvider('getInvalidRequirements')] public function testSetInvalidRequirement($req) { $route = new Route('/{foo}'); @@ -226,9 +225,7 @@ public function testSerialize() $this->assertNotSame($route, $unserialized); } - /** - * @dataProvider provideInlineDefaultAndRequirementCases - */ + #[DataProvider('provideInlineDefaultAndRequirementCases')] public function testInlineDefaultAndRequirement(Route $route, string $expectedPath, string $expectedHost, array $expectedDefaults, array $expectedRequirements) { self::assertSame($expectedPath, $route->getPath()); @@ -323,9 +320,7 @@ public function testSerializedRepresentationKeepsWorking() $this->assertNotSame($route, $unserialized); } - /** - * @dataProvider provideNonLocalizedRoutes - */ + #[DataProvider('provideNonLocalizedRoutes')] public function testLocaleDefaultWithNonLocalizedRoutes(Route $route) { $this->assertNotSame('fr', $route->getDefault('_locale')); @@ -333,9 +328,7 @@ public function testLocaleDefaultWithNonLocalizedRoutes(Route $route) $this->assertSame('fr', $route->getDefault('_locale')); } - /** - * @dataProvider provideLocalizedRoutes - */ + #[DataProvider('provideLocalizedRoutes')] public function testLocaleDefaultWithLocalizedRoutes(Route $route) { $expected = $route->getDefault('_locale'); @@ -345,9 +338,7 @@ public function testLocaleDefaultWithLocalizedRoutes(Route $route) $this->assertSame($expected, $route->getDefault('_locale')); } - /** - * @dataProvider provideNonLocalizedRoutes - */ + #[DataProvider('provideNonLocalizedRoutes')] public function testLocaleRequirementWithNonLocalizedRoutes(Route $route) { $this->assertNotSame('fr', $route->getRequirement('_locale')); @@ -355,9 +346,7 @@ public function testLocaleRequirementWithNonLocalizedRoutes(Route $route) $this->assertSame('fr', $route->getRequirement('_locale')); } - /** - * @dataProvider provideLocalizedRoutes - */ + #[DataProvider('provideLocalizedRoutes')] public function testLocaleRequirementWithLocalizedRoutes(Route $route) { $expected = $route->getRequirement('_locale'); From ae703383cbe591c9f9053a27dd03804ef4ba31af Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sun, 10 Aug 2025 00:28:14 +0200 Subject: [PATCH 13/25] chore: heredoc indentation as of PHP 7.3 https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc --- .../Dumper/CompiledUrlGeneratorDumper.php | 10 +- Matcher/Dumper/CompiledUrlMatcherDumper.php | 28 ++--- .../Dumper/StaticPrefixCollectionTest.php | 110 +++++++++--------- 3 files changed, 74 insertions(+), 74 deletions(-) diff --git a/Generator/Dumper/CompiledUrlGeneratorDumper.php b/Generator/Dumper/CompiledUrlGeneratorDumper.php index 555c5bfb..881a6d50 100644 --- a/Generator/Dumper/CompiledUrlGeneratorDumper.php +++ b/Generator/Dumper/CompiledUrlGeneratorDumper.php @@ -91,14 +91,14 @@ public function getCompiledAliases(): array public function dump(array $options = []): string { return <<generateDeclaredRoutes()} -]; + return [{$this->generateDeclaredRoutes()} + ]; -EOF; + EOF; } /** diff --git a/Matcher/Dumper/CompiledUrlMatcherDumper.php b/Matcher/Dumper/CompiledUrlMatcherDumper.php index b719e755..4d0b9b6f 100644 --- a/Matcher/Dumper/CompiledUrlMatcherDumper.php +++ b/Matcher/Dumper/CompiledUrlMatcherDumper.php @@ -37,17 +37,17 @@ class CompiledUrlMatcherDumper extends MatcherDumper public function dump(array $options = []): string { return <<generateCompiledRoutes()}]; + return [ + {$this->generateCompiledRoutes()}]; -EOF; + EOF; } public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider): void @@ -112,12 +112,12 @@ public function getCompiledRoutes(bool $forDump = false): array } $checkConditionCode = <<indent(implode("\n", $conditions), 3)} - } - } -EOF; + static function (\$condition, \$context, \$request, \$params) { // \$checkCondition + switch (\$condition) { + {$this->indent(implode("\n", $conditions), 3)} + } + } + EOF; $compiledRoutes[4] = $forDump ? $checkConditionCode.",\n" : eval('return '.$checkConditionCode.';'); } else { $compiledRoutes[4] = $forDump ? " null, // \$checkCondition\n" : null; diff --git a/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php b/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php index f88fcdba..939aee5b 100644 --- a/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php +++ b/Tests/Matcher/Dumper/StaticPrefixCollectionTest.php @@ -43,10 +43,10 @@ public static function routeProvider() ['/leading/segment/', 'leading_segment'], ], << [ [ @@ -55,11 +55,11 @@ public static function routeProvider() ['/prefix/segment/bb', 'leading_segment'], ], << prefix_segment --> leading_segment -EOF, + root + /prefix/segment/ + -> prefix_segment + -> leading_segment + EOF, ], 'Nested - contains item at intersection' => [ [ @@ -68,11 +68,11 @@ public static function routeProvider() ['/prefix/segment/bb', 'leading_segment'], ], << prefix_segment --> leading_segment -EOF, + root + /prefix/segment/ + -> prefix_segment + -> leading_segment + EOF, ], 'Simple one level nesting' => [ [ @@ -82,12 +82,12 @@ public static function routeProvider() ['/group/other/', 'other_segment'], ], << nested_segment --> some_segment --> other_segment -EOF, + root + /group/ + -> nested_segment + -> some_segment + -> other_segment + EOF, ], 'Retain matching order with groups' => [ [ @@ -100,16 +100,16 @@ public static function routeProvider() ['/group/ff/', 'ff'], ], << aa --> bb --> cc -root -/group/ --> dd --> ee --> ff -EOF, + /group/ + -> aa + -> bb + -> cc + root + /group/ + -> dd + -> ee + -> ff + EOF, ], 'Retain complex matching order with groups at base' => [ [ @@ -126,22 +126,22 @@ public static function routeProvider() ['/aaa/333/', 'third_aaa'], ], << first_aaa --> second_aaa --> third_aaa -/prefixed/ --> /prefixed/group/ --> -> aa --> -> bb --> -> cc --> root --> /prefixed/group/ --> -> dd --> -> ee --> -> ff --> parent -EOF, + /aaa/ + -> first_aaa + -> second_aaa + -> third_aaa + /prefixed/ + -> /prefixed/group/ + -> -> aa + -> -> bb + -> -> cc + -> root + -> /prefixed/group/ + -> -> dd + -> -> ee + -> -> ff + -> parent + EOF, ], 'Group regardless of segments' => [ @@ -154,15 +154,15 @@ public static function routeProvider() ['/group-cc/', 'g3'], ], << a1 --> a2 --> a3 -/group- --> g1 --> g2 --> g3 -EOF, + /aaa- + -> a1 + -> a2 + -> a3 + /group- + -> g1 + -> g2 + -> g3 + EOF, ], ]; } From 9adbd24c853c9ada6dd8f6ad2b7c7adacae057aa Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 1 Aug 2025 14:58:41 +0200 Subject: [PATCH 14/25] run tests with PHPUnit 12.3 --- Tests/RouteCompilerTest.php | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/Tests/RouteCompilerTest.php b/Tests/RouteCompilerTest.php index af8941d6..51bc47ed 100644 --- a/Tests/RouteCompilerTest.php +++ b/Tests/RouteCompilerTest.php @@ -202,39 +202,47 @@ public static function provideCompileImplicitUtf8Data() [ 'Static UTF-8 route', ['/foé'], - '/foé', '{^/foé$}sDu', [], [ + '/foé', + '{^/foé$}sDu', + [], + [ ['text', '/foé'], ], - 'patterns', ], [ 'Route with an implicit UTF-8 requirement', ['/{bar}', ['bar' => null], ['bar' => 'é']], - '', '{^/(?Pé)?$}sDu', ['bar'], [ + '', + '{^/(?Pé)?$}sDu', + ['bar'], + [ ['variable', '/', 'é', 'bar', true], ], - 'requirements', ], [ 'Route with a UTF-8 class requirement', ['/{bar}', ['bar' => null], ['bar' => '\pM']], - '', '{^/(?P\pM)?$}sDu', ['bar'], [ + '', + '{^/(?P\pM)?$}sDu', + ['bar'], + [ ['variable', '/', '\pM', 'bar', true], ], - 'requirements', ], [ 'Route with a UTF-8 separator', ['/foo/{bar}§{_format}', [], [], ['compiler_class' => Utf8RouteCompiler::class]], - '/foo', '{^/foo/(?P[^/§]++)§(?P<_format>[^/]++)$}sDu', ['bar', '_format'], [ + '/foo', + '{^/foo/(?P[^/§]++)§(?P<_format>[^/]++)$}sDu', + ['bar', '_format'], + [ ['variable', '§', '[^/]++', '_format', true], ['variable', '/', '[^/§]++', 'bar', true], ['text', '/foo'], ], - 'patterns', ], ]; } From 1b8f64bfeb41c7244ce12b92784695fe6d4f8ee8 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 14 Aug 2025 09:36:33 +0200 Subject: [PATCH 15/25] Replace __sleep/wakeup() by __(un)serialize() when BC isn't a concern --- Loader/Configurator/CollectionConfigurator.php | 4 ++-- Loader/Configurator/ImportConfigurator.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Loader/Configurator/CollectionConfigurator.php b/Loader/Configurator/CollectionConfigurator.php index 4b83b0ff..d1553213 100644 --- a/Loader/Configurator/CollectionConfigurator.php +++ b/Loader/Configurator/CollectionConfigurator.php @@ -36,12 +36,12 @@ public function __construct( $this->route = new Route(''); } - public function __sleep(): array + public function __serialize(): array { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } - public function __wakeup(): void + public function __unserialize(array $data): void { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } diff --git a/Loader/Configurator/ImportConfigurator.php b/Loader/Configurator/ImportConfigurator.php index 45d1f6dc..b10bdf05 100644 --- a/Loader/Configurator/ImportConfigurator.php +++ b/Loader/Configurator/ImportConfigurator.php @@ -29,12 +29,12 @@ public function __construct( $this->route = $route; } - public function __sleep(): array + public function __serialize(): array { throw new \BadMethodCallException('Cannot serialize '.__CLASS__); } - public function __wakeup(): void + public function __unserialize(array $data): void { throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } From 0565daeaf865898a9f8fccbe00e4100f4174a4db Mon Sep 17 00:00:00 2001 From: Santiago San Martin Date: Thu, 7 Aug 2025 18:59:46 -0300 Subject: [PATCH 16/25] [Routing] allow setting multiple envs in `#[Route]` attribute --- Attribute/Route.php | 36 +++++++++++++++++-- CHANGELOG.md | 1 + Loader/AttributeClassLoader.php | 6 ++-- .../AttributeFixtures/RouteWithEnv.php | 15 ++++++++ Tests/Loader/AttributeClassLoaderTest.php | 4 ++- 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/Attribute/Route.php b/Attribute/Route.php index 003bbe64..1ad37e17 100644 --- a/Attribute/Route.php +++ b/Attribute/Route.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Routing\Attribute; +use Symfony\Component\Routing\Exception\LogicException; + /** * @author Fabien Potencier * @author Alexander M. Turek @@ -21,6 +23,8 @@ class Route private ?string $path = null; private array $localizedPaths = []; private array $methods; + /** @var string[] */ + private array $env; private array $schemes; /** * @var (string|DeprecatedAlias)[] @@ -42,7 +46,7 @@ class Route * @param string|null $format The format returned by the route (i.e. "json", "xml") * @param bool|null $utf8 Whether the route accepts UTF-8 in its parameters * @param bool|null $stateless Whether the route is defined as stateless or stateful, @see https://symfony.com/doc/current/routing.html#stateless-routes - * @param string|null $env The env in which the route is defined (i.e. "dev", "test", "prod") + * @param string|string[]|null $env The env(s) in which the route is defined (i.e. "dev", "test", "prod", ["dev", "test"]) * @param string|DeprecatedAlias|(string|DeprecatedAlias)[] $alias The list of aliases for this route */ public function __construct( @@ -60,7 +64,7 @@ public function __construct( ?string $format = null, ?bool $utf8 = null, ?bool $stateless = null, - private ?string $env = null, + string|array|null $env = null, string|DeprecatedAlias|array $alias = [], ) { if (\is_array($path)) { @@ -71,6 +75,7 @@ public function __construct( $this->setMethods($methods); $this->setSchemes($schemes); $this->setAliases($alias); + $this->setEnvs((array) $env); if (null !== $locale) { $this->defaults['_locale'] = $locale; @@ -199,12 +204,37 @@ public function getPriority(): ?int return $this->priority; } + /** + * @deprecated since Symfony 7.4, use the {@see setEnvs()} method instead + */ public function setEnv(?string $env): void { - $this->env = $env; + trigger_deprecation('symfony/routing', '7.4', 'The "%s()" method is deprecated, use "setEnvs()" instead.', __METHOD__); + $this->env = (array) $env; } + /** + * @deprecated since Symfony 7.4, use {@see getEnvs()} method instead + */ public function getEnv(): ?string + { + trigger_deprecation('symfony/routing', '7.4', 'The "%s()" method is deprecated, use "getEnvs()" instead.', __METHOD__); + if (!$this->env) { + return null; + } + if (\count($this->env) > 1) { + throw new LogicException(\sprintf('The "env" property has %d environments. Use "getEnvs()" to get all of them.', \count($this->env))); + } + + return $this->env[0]; + } + + public function setEnvs(array|string $env): void + { + $this->env = (array) $env; + } + + public function getEnvs(): array { return $this->env; } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ef96d53..3496520b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Allow query-specific parameters in `UrlGenerator` using `_query` + * Add support of multiple env names in the `Symfony\Component\Routing\Attribute\Route` attribute 7.3 --- diff --git a/Loader/AttributeClassLoader.php b/Loader/AttributeClassLoader.php index 254582bf..5fdd7906 100644 --- a/Loader/AttributeClassLoader.php +++ b/Loader/AttributeClassLoader.php @@ -105,7 +105,7 @@ public function load(mixed $class, ?string $type = null): RouteCollection $globals = $this->getGlobals($class); $collection = new RouteCollection(); $collection->addResource(new FileResource($class->getFileName())); - if ($globals['env'] && $this->env !== $globals['env']) { + if ($globals['env'] && !\in_array($this->env, $globals['env'], true)) { return $collection; } $fqcnAlias = false; @@ -161,7 +161,7 @@ public function load(mixed $class, ?string $type = null): RouteCollection */ protected function addRoute(RouteCollection $collection, object $attr, array $globals, \ReflectionClass $class, \ReflectionMethod $method): void { - if ($attr->getEnv() && $attr->getEnv() !== $this->env) { + if ($attr->getEnvs() && !\in_array($this->env, $attr->getEnvs(), true)) { return; } @@ -338,7 +338,7 @@ protected function getGlobals(\ReflectionClass $class): array } $globals['priority'] = $attr->getPriority() ?? 0; - $globals['env'] = $attr->getEnv(); + $globals['env'] = $attr->getEnvs(); foreach ($globals['requirements'] as $placeholder => $requirement) { if (\is_int($placeholder)) { diff --git a/Tests/Fixtures/AttributeFixtures/RouteWithEnv.php b/Tests/Fixtures/AttributeFixtures/RouteWithEnv.php index 31f6c39b..c8f82fdf 100644 --- a/Tests/Fixtures/AttributeFixtures/RouteWithEnv.php +++ b/Tests/Fixtures/AttributeFixtures/RouteWithEnv.php @@ -16,4 +16,19 @@ public function action() public function action2() { } + + #[Route(path: '/path3', name: 'action3', env: ['some-other-env', 'some-other-env-two'])] + public function action3() + { + } + + #[Route(path: '/path4', name: 'action4', env: null)] + public function action4() + { + } + + #[Route(path: '/path5', name: 'action5', env: ['some-other-env', 'some-env'])] + public function action5() + { + } } diff --git a/Tests/Loader/AttributeClassLoaderTest.php b/Tests/Loader/AttributeClassLoaderTest.php index 2fa00e07..8ad0ede6 100644 --- a/Tests/Loader/AttributeClassLoaderTest.php +++ b/Tests/Loader/AttributeClassLoaderTest.php @@ -330,8 +330,10 @@ public function testWhenEnv() $this->setUp('some-env'); $routes = $this->loader->load(RouteWithEnv::class); - $this->assertCount(1, $routes); + $this->assertCount(3, $routes); $this->assertSame('/path', $routes->get('action')->getPath()); + $this->assertSame('/path4', $routes->get('action4')->getPath()); + $this->assertSame('/path5', $routes->get('action5')->getPath()); } public function testMethodsAndSchemes() From 782a740b84747e7f6acc04a4e69949325372b137 Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Wed, 2 Apr 2025 14:07:22 +0200 Subject: [PATCH 17/25] [Routing] Add possibility to create a request context with parameters directly --- RequestContext.php | 3 ++- Tests/RequestContextTest.php | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/RequestContext.php b/RequestContext.php index 5e9e79d9..8c560370 100644 --- a/RequestContext.php +++ b/RequestContext.php @@ -33,7 +33,7 @@ class RequestContext private string $queryString; private array $parameters = []; - public function __construct(string $baseUrl = '', string $method = 'GET', string $host = 'localhost', string $scheme = 'http', int $httpPort = 80, int $httpsPort = 443, string $path = '/', string $queryString = '') + public function __construct(string $baseUrl = '', string $method = 'GET', string $host = 'localhost', string $scheme = 'http', int $httpPort = 80, int $httpsPort = 443, string $path = '/', string $queryString = '', ?array $parameters = null) { $this->setBaseUrl($baseUrl); $this->setMethod($method); @@ -43,6 +43,7 @@ public function __construct(string $baseUrl = '', string $method = 'GET', string $this->setHttpsPort($httpsPort); $this->setPathInfo($path); $this->setQueryString($queryString); + $this->parameters = $parameters ?? $this->parameters; } public static function fromUri(string $uri, string $host = 'localhost', string $scheme = 'http', int $httpPort = 80, int $httpsPort = 443): self diff --git a/Tests/RequestContextTest.php b/Tests/RequestContextTest.php index fcc42ff5..2d206be6 100644 --- a/Tests/RequestContextTest.php +++ b/Tests/RequestContextTest.php @@ -27,7 +27,10 @@ public function testConstruct() 8080, 444, '/baz', - 'bar=foobar' + 'bar=foobar', + [ + 'foo' => 'bar', + ] ); $this->assertEquals('foo', $requestContext->getBaseUrl()); @@ -38,6 +41,20 @@ public function testConstruct() $this->assertSame(444, $requestContext->getHttpsPort()); $this->assertEquals('/baz', $requestContext->getPathInfo()); $this->assertEquals('bar=foobar', $requestContext->getQueryString()); + $this->assertSame(['foo' => 'bar'], $requestContext->getParameters()); + } + + public function testConstructParametersBcLayer() + { + $requestContext = new class() extends RequestContext { + public function __construct() + { + $this->setParameters(['foo' => 'bar']); + parent::__construct(); + } + }; + + $this->assertSame(['foo' => 'bar'], $requestContext->getParameters()); } public function testFromUriWithBaseUrl() From c50e4233f4b7d9659873685fb1eb425ad69d3af3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 20 Aug 2025 09:21:32 +0200 Subject: [PATCH 18/25] Minor tweaks --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3496520b..d97b5738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Allow query-specific parameters in `UrlGenerator` using `_query` * Add support of multiple env names in the `Symfony\Component\Routing\Attribute\Route` attribute + * Add argument `$parameter` to `RequestContext`'s constructor 7.3 --- From 5150c645f0ceb1f55dc44256bfb30824b4c8d783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anderson=20M=C3=BCller?= Date: Wed, 20 Aug 2025 09:49:19 +0200 Subject: [PATCH 19/25] [ROUTING] Fix Typo in CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d97b5738..86af6723 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ CHANGELOG * Allow query-specific parameters in `UrlGenerator` using `_query` * Add support of multiple env names in the `Symfony\Component\Routing\Attribute\Route` attribute - * Add argument `$parameter` to `RequestContext`'s constructor + * Add argument `$parameters` to `RequestContext`'s constructor 7.3 --- From 5d3ebbb0a04056e46c4c3b8f87d3022098e5931e Mon Sep 17 00:00:00 2001 From: Santiago San Martin Date: Wed, 20 Aug 2025 19:12:10 -0300 Subject: [PATCH 20/25] [Routing] Remove deprecated `getEnv` and `setEnv` methods --- Attribute/Route.php | 27 --------------------------- CHANGELOG.md | 1 + 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/Attribute/Route.php b/Attribute/Route.php index 1ad37e17..738387f1 100644 --- a/Attribute/Route.php +++ b/Attribute/Route.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Routing\Attribute; -use Symfony\Component\Routing\Exception\LogicException; - /** * @author Fabien Potencier * @author Alexander M. Turek @@ -204,31 +202,6 @@ public function getPriority(): ?int return $this->priority; } - /** - * @deprecated since Symfony 7.4, use the {@see setEnvs()} method instead - */ - public function setEnv(?string $env): void - { - trigger_deprecation('symfony/routing', '7.4', 'The "%s()" method is deprecated, use "setEnvs()" instead.', __METHOD__); - $this->env = (array) $env; - } - - /** - * @deprecated since Symfony 7.4, use {@see getEnvs()} method instead - */ - public function getEnv(): ?string - { - trigger_deprecation('symfony/routing', '7.4', 'The "%s()" method is deprecated, use "getEnvs()" instead.', __METHOD__); - if (!$this->env) { - return null; - } - if (\count($this->env) > 1) { - throw new LogicException(\sprintf('The "env" property has %d environments. Use "getEnvs()" to get all of them.', \count($this->env))); - } - - return $this->env[0]; - } - public function setEnvs(array|string $env): void { $this->env = (array) $env; diff --git a/CHANGELOG.md b/CHANGELOG.md index ad4a156f..a12433f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Providing a non-array `_query` parameter to `UrlGenerator` causes an `InvalidParameterException` * Remove the protected `AttributeClassLoader::$routeAnnotationClass` property and the `setRouteAnnotationClass()` method, use `AttributeClassLoader::setRouteAttributeClass()` instead + * Remove `getEnv()` and `setEnv()` methods of the `Route` attribute; use the plurialized `getEnvs()` and `setEnvs()` methods instead 7.4 --- From 236276109ce9048eb2957b972858b8e4ef06cf1b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 31 Jul 2025 10:28:51 +0200 Subject: [PATCH 21/25] [DependencyInjection][Routing] Add JSON schema for validating and autocompleting YAML config files --- Loader/schema/routing.schema.json | 136 ++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 Loader/schema/routing.schema.json diff --git a/Loader/schema/routing.schema.json b/Loader/schema/routing.schema.json new file mode 100644 index 00000000..889254b4 --- /dev/null +++ b/Loader/schema/routing.schema.json @@ -0,0 +1,136 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Symfony Routing Configuration", + "description": "Defines the application's URL routes, including imports and environment-specific conditionals.", + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9_.-]+$": { + "oneOf": [ + { "$ref": "#/$defs/routeDefinition" }, + { "$ref": "#/$defs/routeImport" }, + { "$ref": "#/$defs/routeAlias" } + ] + }, + "^when@.+$": { + "$ref": "#", + "description": "A container for routes that are only loaded in a specific environment (e.g., 'when@dev')." + } + }, + "additionalProperties": false, + "$defs": { + "routeDefinition": { + "type": "object", + "properties": { + "path": { + "oneOf": [ + { "type": "string" }, + { "type": "object", "patternProperties": { "^.+$": { "type": "string" } }, "additionalProperties": false } + ], + "description": "The URL path or a map of locale=>path for localized routes." + }, + "controller": { + "type": "string", + "description": "The controller that handles the request, e.g., 'App\\Controller\\BlogController::show'." + }, + "methods": { + "description": "The HTTP method(s) this route matches.", + "oneOf": [ + { "type": "string" }, + { "type": "array", "items": { "type": "string" } } + ] + }, + "requirements": { + "type": "object", + "description": "Regular expression constraints for path parameters.", + "additionalProperties": { "type": "string" } + }, + "defaults": { "type": "object" }, + "options": { "type": "object" }, + "host": { + "oneOf": [ + { "type": "string" }, + { "type": "object", "patternProperties": { "^.+$": { "type": "string" } }, "additionalProperties": false } + ] + }, + "schemes": { + "oneOf": [ + { "type": "string" }, + { "type": "array", "items": { "type": "string" } } + ] + }, + "condition": { "type": "string" }, + "locale": { "type": "string" }, + "format": { "type": "string" }, + "utf8": { "type": "boolean" }, + "stateless": { "type": "boolean" } + }, + "required": ["path"], + "additionalProperties": false + }, + "routeImport": { + "type": "object", + "properties": { + "resource": { "type": "string", "description": "Path to the resource to import." }, + "type": { + "type": "string", + "description": "The type of the resource (e.g., 'attribute', 'annotation', 'yaml')." + }, + "prefix": { + "oneOf": [ + { "type": "string" }, + { "type": "object", "patternProperties": { "^.+$": { "type": "string" } } } + ], + "description": "A URL prefix to apply to all routes from the imported resource." + }, + "name_prefix": { + "type": "string", + "description": "A name prefix to apply to all routes from the imported resource." + }, + "requirements": { "type": "object", "additionalProperties": { "type": "string" } }, + "defaults": { "type": "object" }, + "options": { "type": "object" }, + "host": { + "oneOf": [ + { "type": "string" }, + { "type": "object", "patternProperties": { "^.+$": { "type": "string" } }, "additionalProperties": false } + ] + }, + "schemes": { + "oneOf": [ + { "type": "string" }, + { "type": "array", "items": { "type": "string" } } + ] + }, + "condition": { "type": "string" }, + "trailing_slash_on_root": { "type": "boolean" }, + "methods": { "oneOf": [ { "type": "string" }, { "type": "array", "items": { "type": "string" } } ] }, + "locale": { "type": "string" }, + "format": { "type": "string" }, + "utf8": { "type": "boolean" }, + "exclude": { "oneOf": [ { "type": "string" }, { "type": "array", "items": { "type": "string" } } ] }, + "stateless": { "type": "boolean" }, + "controller": { "type": "string" } + }, + "required": ["resource"], + "additionalProperties": false + }, + "routeAlias": { + "type": "object", + "properties": { + "alias": { "type": "string" }, + "deprecated": { + "type": "object", + "properties": { + "package": { "type": "string" }, + "version": { "type": "string" }, + "message": { "type": "string" } + }, + "required": ["package", "version"], + "additionalProperties": false + } + }, + "required": ["alias"], + "additionalProperties": false + } + } +} From cab332ef1900e2c6222a32fd56436261e8c74759 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 21 Aug 2025 15:48:39 +0200 Subject: [PATCH 22/25] [Routing][Serializer] Deprecate annotation aliases and getters and setters in favor of public properties on attributes --- Annotation/Route.php | 3 + Attribute/DeprecatedAlias.php | 12 ++-- Attribute/Route.php | 109 +++++++++++++++++--------------- CHANGELOG.md | 2 + Loader/AttributeClassLoader.php | 79 +++++++++++------------ Tests/Attribute/RouteTest.php | 26 ++++---- 6 files changed, 123 insertions(+), 108 deletions(-) diff --git a/Annotation/Route.php b/Annotation/Route.php index dda3bdad..1a85a703 100644 --- a/Annotation/Route.php +++ b/Annotation/Route.php @@ -16,6 +16,9 @@ class_exists(\Symfony\Component\Routing\Attribute\Route::class); if (false) { + /** + * @deprecated since Symfony 7.4, use {@see \Symfony\Component\Routing\Attribute\Route} instead + */ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class Route extends \Symfony\Component\Routing\Attribute\Route { diff --git a/Attribute/DeprecatedAlias.php b/Attribute/DeprecatedAlias.php index ae5a6821..5861f2a2 100644 --- a/Attribute/DeprecatedAlias.php +++ b/Attribute/DeprecatedAlias.php @@ -17,28 +17,32 @@ class DeprecatedAlias { public function __construct( - private string $aliasName, - private string $package, - private string $version, - private string $message = '', + public readonly string $aliasName, + public readonly string $package, + public readonly string $version, + public readonly string $message = '', ) { } + #[\Deprecated('Use the "message" property instead', 'symfony/routing:7.4')] public function getMessage(): string { return $this->message; } + #[\Deprecated('Use the "aliasName" property instead', 'symfony/routing:7.4')] public function getAliasName(): string { return $this->aliasName; } + #[\Deprecated('Use the "package" property instead', 'symfony/routing:7.4')] public function getPackage(): string { return $this->package; } + #[\Deprecated('Use the "version" property instead', 'symfony/routing:7.4')] public function getVersion(): string { return $this->version; diff --git a/Attribute/Route.php b/Attribute/Route.php index 1ad37e17..172b621a 100644 --- a/Attribute/Route.php +++ b/Attribute/Route.php @@ -20,16 +20,17 @@ #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class Route { - private ?string $path = null; - private array $localizedPaths = []; - private array $methods; /** @var string[] */ - private array $env; - private array $schemes; - /** - * @var (string|DeprecatedAlias)[] - */ - private array $aliases = []; + public array $methods; + + /** @var string[] */ + public array $envs; + + /** @var string[] */ + public array $schemes; + + /** @var (string|DeprecatedAlias)[] */ + public array $aliases = []; /** * @param string|array|null $path The route path (i.e. "/user/login") @@ -50,16 +51,16 @@ class Route * @param string|DeprecatedAlias|(string|DeprecatedAlias)[] $alias The list of aliases for this route */ public function __construct( - string|array|null $path = null, - private ?string $name = null, - private array $requirements = [], - private array $options = [], - private array $defaults = [], - private ?string $host = null, + public string|array|null $path = null, + public ?string $name = null, + public array $requirements = [], + public array $options = [], + public array $defaults = [], + public ?string $host = null, array|string $methods = [], array|string $schemes = [], - private ?string $condition = null, - private ?int $priority = null, + public ?string $condition = null, + public ?int $priority = null, ?string $locale = null, ?string $format = null, ?bool $utf8 = null, @@ -67,15 +68,11 @@ public function __construct( string|array|null $env = null, string|DeprecatedAlias|array $alias = [], ) { - if (\is_array($path)) { - $this->localizedPaths = $path; - } else { - $this->path = $path; - } - $this->setMethods($methods); - $this->setSchemes($schemes); - $this->setAliases($alias); - $this->setEnvs((array) $env); + $this->path = $path; + $this->methods = (array) $methods; + $this->schemes = (array) $schemes; + $this->envs = (array) $env; + $this->aliases = \is_array($alias) ? $alias : [$alias]; if (null !== $locale) { $this->defaults['_locale'] = $locale; @@ -94,154 +91,161 @@ public function __construct( } } + #[\Deprecated('Use the "path" property instead', 'symfony/routing:7.4')] public function setPath(string $path): void { $this->path = $path; } + #[\Deprecated('Use the "path" property instead', 'symfony/routing:7.4')] public function getPath(): ?string { - return $this->path; + return \is_array($this->path) ? null : $this->path; } + #[\Deprecated('Use the "path" property instead', 'symfony/routing:7.4')] public function setLocalizedPaths(array $localizedPaths): void { - $this->localizedPaths = $localizedPaths; + $this->path = $localizedPaths; } + #[\Deprecated('Use the "path" property instead', 'symfony/routing:7.4')] public function getLocalizedPaths(): array { - return $this->localizedPaths; + return \is_array($this->path) ? $this->path : []; } + #[\Deprecated('Use the "host" property instead', 'symfony/routing:7.4')] public function setHost(string $pattern): void { $this->host = $pattern; } + #[\Deprecated('Use the "host" property instead', 'symfony/routing:7.4')] public function getHost(): ?string { return $this->host; } + #[\Deprecated('Use the "name" property instead', 'symfony/routing:7.4')] public function setName(string $name): void { $this->name = $name; } + #[\Deprecated('Use the "name" property instead', 'symfony/routing:7.4')] public function getName(): ?string { return $this->name; } + #[\Deprecated('Use the "requirements" property instead', 'symfony/routing:7.4')] public function setRequirements(array $requirements): void { $this->requirements = $requirements; } + #[\Deprecated('Use the "requirements" property instead', 'symfony/routing:7.4')] public function getRequirements(): array { return $this->requirements; } + #[\Deprecated('Use the "options" property instead', 'symfony/routing:7.4')] public function setOptions(array $options): void { $this->options = $options; } + #[\Deprecated('Use the "options" property instead', 'symfony/routing:7.4')] public function getOptions(): array { return $this->options; } + #[\Deprecated('Use the "defaults" property instead', 'symfony/routing:7.4')] public function setDefaults(array $defaults): void { $this->defaults = $defaults; } + #[\Deprecated('Use the "defaults" property instead', 'symfony/routing:7.4')] public function getDefaults(): array { return $this->defaults; } + #[\Deprecated('Use the "schemes" property instead', 'symfony/routing:7.4')] public function setSchemes(array|string $schemes): void { $this->schemes = (array) $schemes; } + #[\Deprecated('Use the "schemes" property instead', 'symfony/routing:7.4')] public function getSchemes(): array { return $this->schemes; } + #[\Deprecated('Use the "methods" property instead', 'symfony/routing:7.4')] public function setMethods(array|string $methods): void { $this->methods = (array) $methods; } + #[\Deprecated('Use the "methods" property instead', 'symfony/routing:7.4')] public function getMethods(): array { return $this->methods; } + #[\Deprecated('Use the "condition" property instead', 'symfony/routing:7.4')] public function setCondition(?string $condition): void { $this->condition = $condition; } + #[\Deprecated('Use the "condition" property instead', 'symfony/routing:7.4')] public function getCondition(): ?string { return $this->condition; } + #[\Deprecated('Use the "priority" property instead', 'symfony/routing:7.4')] public function setPriority(int $priority): void { $this->priority = $priority; } + #[\Deprecated('Use the "priority" property instead', 'symfony/routing:7.4')] public function getPriority(): ?int { return $this->priority; } - /** - * @deprecated since Symfony 7.4, use the {@see setEnvs()} method instead - */ + #[\Deprecated('Use the "envs" property instead', 'symfony/routing:7.4')] public function setEnv(?string $env): void { - trigger_deprecation('symfony/routing', '7.4', 'The "%s()" method is deprecated, use "setEnvs()" instead.', __METHOD__); - $this->env = (array) $env; + $this->envs = (array) $env; } - /** - * @deprecated since Symfony 7.4, use {@see getEnvs()} method instead - */ + #[\Deprecated('Use the "envs" property instead', 'symfony/routing:7.4')] public function getEnv(): ?string { - trigger_deprecation('symfony/routing', '7.4', 'The "%s()" method is deprecated, use "getEnvs()" instead.', __METHOD__); - if (!$this->env) { + if (!$this->envs) { return null; } - if (\count($this->env) > 1) { - throw new LogicException(\sprintf('The "env" property has %d environments. Use "getEnvs()" to get all of them.', \count($this->env))); + if (\count($this->envs) > 1) { + throw new LogicException(\sprintf('The "env" property has %d environments. Use "getEnvs()" to get all of them.', \count($this->envs))); } - return $this->env[0]; - } - - public function setEnvs(array|string $env): void - { - $this->env = (array) $env; - } - - public function getEnvs(): array - { - return $this->env; + return $this->envs[0]; } /** * @return (string|DeprecatedAlias)[] */ + #[\Deprecated('Use the "aliases" property instead', 'symfony/routing:7.4')] public function getAliases(): array { return $this->aliases; @@ -250,6 +254,7 @@ public function getAliases(): array /** * @param string|DeprecatedAlias|(string|DeprecatedAlias)[] $aliases */ + #[\Deprecated('Use the "aliases" property instead', 'symfony/routing:7.4')] public function setAliases(string|DeprecatedAlias|array $aliases): void { $this->aliases = \is_array($aliases) ? $aliases : [$aliases]; diff --git a/CHANGELOG.md b/CHANGELOG.md index 86af6723..583fd145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ CHANGELOG * Allow query-specific parameters in `UrlGenerator` using `_query` * Add support of multiple env names in the `Symfony\Component\Routing\Attribute\Route` attribute * Add argument `$parameters` to `RequestContext`'s constructor + * Deprecate class aliases in the `Annotation` namespace, use attributes instead + * Deprecate getters and setters in attribute classes in favor of public properties 7.3 --- diff --git a/Loader/AttributeClassLoader.php b/Loader/AttributeClassLoader.php index 5fdd7906..9c133bd9 100644 --- a/Loader/AttributeClassLoader.php +++ b/Loader/AttributeClassLoader.php @@ -112,7 +112,7 @@ public function load(mixed $class, ?string $type = null): RouteCollection if (!$class->hasMethod('__invoke')) { foreach ($this->getAttributes($class) as $attr) { - if ($attr->getAliases()) { + if ($attr->aliases) { throw new InvalidArgumentException(\sprintf('Route aliases cannot be used on non-invokable class "%s".', $class->getName())); } } @@ -161,14 +161,14 @@ public function load(mixed $class, ?string $type = null): RouteCollection */ protected function addRoute(RouteCollection $collection, object $attr, array $globals, \ReflectionClass $class, \ReflectionMethod $method): void { - if ($attr->getEnvs() && !\in_array($this->env, $attr->getEnvs(), true)) { + if ($attr->envs && !\in_array($this->env, $attr->envs, true)) { return; } - $name = $attr->getName() ?? $this->getDefaultRouteName($class, $method); + $name = $attr->name ?? $this->getDefaultRouteName($class, $method); $name = $globals['name'].$name; - $requirements = $attr->getRequirements(); + $requirements = $attr->requirements; foreach ($requirements as $placeholder => $requirement) { if (\is_int($placeholder)) { @@ -176,17 +176,17 @@ protected function addRoute(RouteCollection $collection, object $attr, array $gl } } - $defaults = array_replace($globals['defaults'], $attr->getDefaults()); + $defaults = array_replace($globals['defaults'], $attr->defaults); $requirements = array_replace($globals['requirements'], $requirements); - $options = array_replace($globals['options'], $attr->getOptions()); - $schemes = array_unique(array_merge($globals['schemes'], $attr->getSchemes())); - $methods = array_unique(array_merge($globals['methods'], $attr->getMethods())); + $options = array_replace($globals['options'], $attr->options); + $schemes = array_unique(array_merge($globals['schemes'], $attr->schemes)); + $methods = array_unique(array_merge($globals['methods'], $attr->methods)); - $host = $attr->getHost() ?? $globals['host']; - $condition = $attr->getCondition() ?? $globals['condition']; - $priority = $attr->getPriority() ?? $globals['priority']; + $host = $attr->host ?? $globals['host']; + $condition = $attr->condition ?? $globals['condition']; + $priority = $attr->priority ?? $globals['priority']; - $path = $attr->getLocalizedPaths() ?: $attr->getPath(); + $path = $attr->path; $prefix = $globals['localized_paths'] ?: $globals['path']; $paths = []; @@ -241,13 +241,13 @@ protected function addRoute(RouteCollection $collection, object $attr, array $gl } else { $collection->add($name, $route, $priority); } - foreach ($attr->getAliases() as $aliasAttribute) { + foreach ($attr->aliases as $aliasAttribute) { if ($aliasAttribute instanceof DeprecatedAlias) { - $alias = $collection->addAlias($aliasAttribute->getAliasName(), $name); + $alias = $collection->addAlias($aliasAttribute->aliasName, $name); $alias->setDeprecated( - $aliasAttribute->getPackage(), - $aliasAttribute->getVersion(), - $aliasAttribute->getMessage() + $aliasAttribute->package, + $aliasAttribute->version, + $aliasAttribute->message ); continue; } @@ -299,46 +299,47 @@ protected function getGlobals(\ReflectionClass $class): array if ($attribute = $class->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) { $attr = $attribute->newInstance(); - if (null !== $attr->getName()) { - $globals['name'] = $attr->getName(); + if (null !== $attr->name) { + $globals['name'] = $attr->name; } - if (null !== $attr->getPath()) { - $globals['path'] = $attr->getPath(); + if (\is_string($attr->path)) { + $globals['path'] = $attr->path; + $globals['localized_paths'] = []; + } else { + $globals['localized_paths'] = $attr->path ?? []; } - $globals['localized_paths'] = $attr->getLocalizedPaths(); - - if (null !== $attr->getRequirements()) { - $globals['requirements'] = $attr->getRequirements(); + if (null !== $attr->requirements) { + $globals['requirements'] = $attr->requirements; } - if (null !== $attr->getOptions()) { - $globals['options'] = $attr->getOptions(); + if (null !== $attr->options) { + $globals['options'] = $attr->options; } - if (null !== $attr->getDefaults()) { - $globals['defaults'] = $attr->getDefaults(); + if (null !== $attr->defaults) { + $globals['defaults'] = $attr->defaults; } - if (null !== $attr->getSchemes()) { - $globals['schemes'] = $attr->getSchemes(); + if (null !== $attr->schemes) { + $globals['schemes'] = $attr->schemes; } - if (null !== $attr->getMethods()) { - $globals['methods'] = $attr->getMethods(); + if (null !== $attr->methods) { + $globals['methods'] = $attr->methods; } - if (null !== $attr->getHost()) { - $globals['host'] = $attr->getHost(); + if (null !== $attr->host) { + $globals['host'] = $attr->host; } - if (null !== $attr->getCondition()) { - $globals['condition'] = $attr->getCondition(); + if (null !== $attr->condition) { + $globals['condition'] = $attr->condition; } - $globals['priority'] = $attr->getPriority() ?? 0; - $globals['env'] = $attr->getEnvs(); + $globals['priority'] = $attr->priority ?? 0; + $globals['env'] = $attr->envs; foreach ($globals['requirements'] as $placeholder => $requirement) { if (\is_int($placeholder)) { diff --git a/Tests/Attribute/RouteTest.php b/Tests/Attribute/RouteTest.php index 8ff0a4dd..1ca2b98b 100644 --- a/Tests/Attribute/RouteTest.php +++ b/Tests/Attribute/RouteTest.php @@ -19,27 +19,27 @@ class RouteTest extends TestCase { #[DataProvider('getValidParameters')] - public function testLoadFromAttribute(string $methodName, string $getter, mixed $expectedReturn) + public function testLoadFromAttribute(string $methodName, string $property, mixed $expectedReturn) { $route = (new \ReflectionMethod(FooController::class, $methodName))->getAttributes(Route::class)[0]->newInstance(); - $this->assertEquals($route->$getter(), $expectedReturn); + $this->assertEquals($route->$property, $expectedReturn); } public static function getValidParameters(): iterable { return [ - ['simplePath', 'getPath', '/Blog'], - ['localized', 'getLocalizedPaths', ['nl' => '/hier', 'en' => '/here']], - ['requirements', 'getRequirements', ['locale' => 'en']], - ['options', 'getOptions', ['compiler_class' => 'RouteCompiler']], - ['name', 'getName', 'blog_index'], - ['defaults', 'getDefaults', ['_controller' => 'MyBlogBundle:Blog:index']], - ['schemes', 'getSchemes', ['https']], - ['methods', 'getMethods', ['GET', 'POST']], - ['host', 'getHost', '{locale}.example.com'], - ['condition', 'getCondition', 'context.getMethod() == \'GET\''], - ['alias', 'getAliases', ['alias', 'completely_different_name']], + ['simplePath', 'path', '/Blog'], + ['localized', 'path', ['nl' => '/hier', 'en' => '/here']], + ['requirements', 'requirements', ['locale' => 'en']], + ['options', 'options', ['compiler_class' => 'RouteCompiler']], + ['name', 'name', 'blog_index'], + ['defaults', 'defaults', ['_controller' => 'MyBlogBundle:Blog:index']], + ['schemes', 'schemes', ['https']], + ['methods', 'methods', ['GET', 'POST']], + ['host', 'host', '{locale}.example.com'], + ['condition', 'condition', 'context.getMethod() == \'GET\''], + ['alias', 'aliases', ['alias', 'completely_different_name']], ]; } } From 0ca1a7e73e29907843f683a54b58aa002f4107cf Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 21 Aug 2025 16:05:23 +0200 Subject: [PATCH 23/25] [Routing][Serializer] Remove annotation aliases and getters and setters in favor of public properties on attributes --- Annotation/Route.php | 26 --- Attribute/DeprecatedAlias.php | 24 --- Attribute/Route.php | 173 ------------------ CHANGELOG.md | 3 +- .../RouteWithPriorityController.php | 2 +- 5 files changed, 3 insertions(+), 225 deletions(-) delete mode 100644 Annotation/Route.php diff --git a/Annotation/Route.php b/Annotation/Route.php deleted file mode 100644 index 1a85a703..00000000 --- a/Annotation/Route.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Routing\Annotation; - -// do not deprecate in 6.4/7.0, to make it easier for the ecosystem to support 6.4, 7.4 and 8.0 simultaneously - -class_exists(\Symfony\Component\Routing\Attribute\Route::class); - -if (false) { - /** - * @deprecated since Symfony 7.4, use {@see \Symfony\Component\Routing\Attribute\Route} instead - */ - #[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] - class Route extends \Symfony\Component\Routing\Attribute\Route - { - } -} diff --git a/Attribute/DeprecatedAlias.php b/Attribute/DeprecatedAlias.php index 5861f2a2..50329117 100644 --- a/Attribute/DeprecatedAlias.php +++ b/Attribute/DeprecatedAlias.php @@ -23,28 +23,4 @@ public function __construct( public readonly string $message = '', ) { } - - #[\Deprecated('Use the "message" property instead', 'symfony/routing:7.4')] - public function getMessage(): string - { - return $this->message; - } - - #[\Deprecated('Use the "aliasName" property instead', 'symfony/routing:7.4')] - public function getAliasName(): string - { - return $this->aliasName; - } - - #[\Deprecated('Use the "package" property instead', 'symfony/routing:7.4')] - public function getPackage(): string - { - return $this->package; - } - - #[\Deprecated('Use the "version" property instead', 'symfony/routing:7.4')] - public function getVersion(): string - { - return $this->version; - } } diff --git a/Attribute/Route.php b/Attribute/Route.php index b0598b30..0d0fc807 100644 --- a/Attribute/Route.php +++ b/Attribute/Route.php @@ -88,177 +88,4 @@ public function __construct( $this->defaults['_stateless'] = $stateless; } } - - #[\Deprecated('Use the "path" property instead', 'symfony/routing:7.4')] - public function setPath(string $path): void - { - $this->path = $path; - } - - #[\Deprecated('Use the "path" property instead', 'symfony/routing:7.4')] - public function getPath(): ?string - { - return \is_array($this->path) ? null : $this->path; - } - - #[\Deprecated('Use the "path" property instead', 'symfony/routing:7.4')] - public function setLocalizedPaths(array $localizedPaths): void - { - $this->path = $localizedPaths; - } - - #[\Deprecated('Use the "path" property instead', 'symfony/routing:7.4')] - public function getLocalizedPaths(): array - { - return \is_array($this->path) ? $this->path : []; - } - - #[\Deprecated('Use the "host" property instead', 'symfony/routing:7.4')] - public function setHost(string $pattern): void - { - $this->host = $pattern; - } - - #[\Deprecated('Use the "host" property instead', 'symfony/routing:7.4')] - public function getHost(): ?string - { - return $this->host; - } - - #[\Deprecated('Use the "name" property instead', 'symfony/routing:7.4')] - public function setName(string $name): void - { - $this->name = $name; - } - - #[\Deprecated('Use the "name" property instead', 'symfony/routing:7.4')] - public function getName(): ?string - { - return $this->name; - } - - #[\Deprecated('Use the "requirements" property instead', 'symfony/routing:7.4')] - public function setRequirements(array $requirements): void - { - $this->requirements = $requirements; - } - - #[\Deprecated('Use the "requirements" property instead', 'symfony/routing:7.4')] - public function getRequirements(): array - { - return $this->requirements; - } - - #[\Deprecated('Use the "options" property instead', 'symfony/routing:7.4')] - public function setOptions(array $options): void - { - $this->options = $options; - } - - #[\Deprecated('Use the "options" property instead', 'symfony/routing:7.4')] - public function getOptions(): array - { - return $this->options; - } - - #[\Deprecated('Use the "defaults" property instead', 'symfony/routing:7.4')] - public function setDefaults(array $defaults): void - { - $this->defaults = $defaults; - } - - #[\Deprecated('Use the "defaults" property instead', 'symfony/routing:7.4')] - public function getDefaults(): array - { - return $this->defaults; - } - - #[\Deprecated('Use the "schemes" property instead', 'symfony/routing:7.4')] - public function setSchemes(array|string $schemes): void - { - $this->schemes = (array) $schemes; - } - - #[\Deprecated('Use the "schemes" property instead', 'symfony/routing:7.4')] - public function getSchemes(): array - { - return $this->schemes; - } - - #[\Deprecated('Use the "methods" property instead', 'symfony/routing:7.4')] - public function setMethods(array|string $methods): void - { - $this->methods = (array) $methods; - } - - #[\Deprecated('Use the "methods" property instead', 'symfony/routing:7.4')] - public function getMethods(): array - { - return $this->methods; - } - - #[\Deprecated('Use the "condition" property instead', 'symfony/routing:7.4')] - public function setCondition(?string $condition): void - { - $this->condition = $condition; - } - - #[\Deprecated('Use the "condition" property instead', 'symfony/routing:7.4')] - public function getCondition(): ?string - { - return $this->condition; - } - - #[\Deprecated('Use the "priority" property instead', 'symfony/routing:7.4')] - public function setPriority(int $priority): void - { - $this->priority = $priority; - } - - #[\Deprecated('Use the "priority" property instead', 'symfony/routing:7.4')] - public function getPriority(): ?int - { - return $this->priority; - } - - #[\Deprecated('Use the "envs" property instead', 'symfony/routing:7.4')] - public function setEnv(?string $env): void - { - $this->envs = (array) $env; - } - - #[\Deprecated('Use the "envs" property instead', 'symfony/routing:7.4')] - public function getEnv(): ?string - { - if (!$this->envs) { - return null; - } - if (\count($this->envs) > 1) { - throw new LogicException(\sprintf('The "env" property has %d environments. Use "getEnvs()" to get all of them.', \count($this->envs))); - } - - return $this->envs[0]; - } - - /** - * @return (string|DeprecatedAlias)[] - */ - #[\Deprecated('Use the "aliases" property instead', 'symfony/routing:7.4')] - public function getAliases(): array - { - return $this->aliases; - } - - /** - * @param string|DeprecatedAlias|(string|DeprecatedAlias)[] $aliases - */ - #[\Deprecated('Use the "aliases" property instead', 'symfony/routing:7.4')] - public function setAliases(string|DeprecatedAlias|array $aliases): void - { - $this->aliases = \is_array($aliases) ? $aliases : [$aliases]; - } -} - -if (!class_exists(\Symfony\Component\Routing\Annotation\Route::class, false)) { - class_alias(Route::class, \Symfony\Component\Routing\Annotation\Route::class); } diff --git a/CHANGELOG.md b/CHANGELOG.md index f673aa04..222995e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ CHANGELOG * Providing a non-array `_query` parameter to `UrlGenerator` causes an `InvalidParameterException` * Remove the protected `AttributeClassLoader::$routeAnnotationClass` property and the `setRouteAnnotationClass()` method, use `AttributeClassLoader::setRouteAttributeClass()` instead - * Remove `getEnv()` and `setEnv()` methods of the `Route` attribute; use the plurialized `getEnvs()` and `setEnvs()` methods instead + * Remove class aliases in the `Annotation` namespace, use attributes instead + * Remove getters and setters in attribute classes in favor of public properties 7.4 --- diff --git a/Tests/Fixtures/AttributeFixtures/RouteWithPriorityController.php b/Tests/Fixtures/AttributeFixtures/RouteWithPriorityController.php index 0cc53242..ba041db7 100644 --- a/Tests/Fixtures/AttributeFixtures/RouteWithPriorityController.php +++ b/Tests/Fixtures/AttributeFixtures/RouteWithPriorityController.php @@ -2,7 +2,7 @@ namespace Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; class RouteWithPriorityController { From 7b3ff9ae5c10aeae08116e89f11fe31d22394df8 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 21 Aug 2025 17:52:30 +0200 Subject: [PATCH 24/25] [Routing][FrameworkBundle] Auto-register routes from attributes found on controller services --- CHANGELOG.md | 1 + DependencyInjection/RoutingControllerPass.php | 40 ++++++++++++ Loader/AttributeServicesLoader.php | 47 ++++++++++++++ .../RoutingControllerPassTest.php | 54 +++++++++++++++ Tests/Loader/AttributeServicesLoaderTest.php | 65 +++++++++++++++++++ 5 files changed, 207 insertions(+) create mode 100644 DependencyInjection/RoutingControllerPass.php create mode 100644 Loader/AttributeServicesLoader.php create mode 100644 Tests/DependencyInjection/RoutingControllerPassTest.php create mode 100644 Tests/Loader/AttributeServicesLoaderTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 583fd145..19346f71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 7.4 --- + * Add `AttributeServicesLoader` and `RoutingControllerPass` to auto-register routes from attributes on services * Allow query-specific parameters in `UrlGenerator` using `_query` * Add support of multiple env names in the `Symfony\Component\Routing\Attribute\Route` attribute * Add argument `$parameters` to `RequestContext`'s constructor diff --git a/DependencyInjection/RoutingControllerPass.php b/DependencyInjection/RoutingControllerPass.php new file mode 100644 index 00000000..9578faf2 --- /dev/null +++ b/DependencyInjection/RoutingControllerPass.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @author Nicolas Grekas + */ +class RoutingControllerPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('routing.loader.attribute.services')) { + return; + } + + $resolve = $container->getParameterBag()->resolveValue(...); + $taggedClasses = []; + foreach ($this->findAndSortTaggedServices('routing.controller', $container) as $id) { + $taggedClasses[$resolve($container->getDefinition($id)->getClass())] = true; + } + + $container->getDefinition('routing.loader.attribute.services') + ->replaceArgument(0, array_keys($taggedClasses)); + } +} diff --git a/Loader/AttributeServicesLoader.php b/Loader/AttributeServicesLoader.php new file mode 100644 index 00000000..d95d9532 --- /dev/null +++ b/Loader/AttributeServicesLoader.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Routing\RouteCollection; + +/** + * Loads routes from a list of tagged classes by delegating to the attribute class loader. + * + * @author Nicolas Grekas + */ +class AttributeServicesLoader extends Loader +{ + /** + * @param class-string[] $taggedClasses + */ + public function __construct( + private array $taggedClasses = [], + ) { + } + + public function load(mixed $resource, ?string $type = null): RouteCollection + { + $collection = new RouteCollection(); + + foreach ($this->taggedClasses as $class) { + $collection->addCollection($this->import($class, 'attribute')); + } + + return $collection; + } + + public function supports(mixed $resource, ?string $type = null): bool + { + return 'tagged_services' === $type && 'attributes' === $resource; + } +} diff --git a/Tests/DependencyInjection/RoutingControllerPassTest.php b/Tests/DependencyInjection/RoutingControllerPassTest.php new file mode 100644 index 00000000..d743a338 --- /dev/null +++ b/Tests/DependencyInjection/RoutingControllerPassTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\Routing\DependencyInjection\RoutingControllerPass; + +class RoutingControllerPassTest extends TestCase +{ + public function testProcessInjectsTaggedControllerClassesOrderedAndUnique() + { + $container = new ContainerBuilder(); + $container->setParameter('ctrl_a.class', CtrlA::class); + + $container->register('routing.loader.attribute.services', \stdClass::class) + ->setArguments([null]); + + $container->register('ctrl_a', '%ctrl_a.class%')->addTag('routing.controller', ['priority' => 10]); + $container->register('ctrl_b', CtrlB::class)->addTag('routing.controller', ['priority' => 20]); + $container->register('ctrl_c', CtrlC::class)->addTag('routing.controller', ['priority' => -5]); + + (new RoutingControllerPass())->process($container); + + $this->assertSame([ + CtrlB::class, + CtrlA::class, + CtrlC::class, + ], $container->getDefinition('routing.loader.attribute.services')->getArgument(0)); + } + + public function testProcessWithNoTaggedControllersSetsEmptyList() + { + $container = new ContainerBuilder(); + + $loaderDef = new Definition(\stdClass::class); + $loaderDef->setArguments([['preexisting']]); + $container->setDefinition('routing.loader.attribute.services', $loaderDef); + + (new RoutingControllerPass())->process($container); + + $this->assertSame([], $container->getDefinition('routing.loader.attribute.services')->getArgument(0)); + } +} diff --git a/Tests/Loader/AttributeServicesLoaderTest.php b/Tests/Loader/AttributeServicesLoaderTest.php new file mode 100644 index 00000000..31d63ec7 --- /dev/null +++ b/Tests/Loader/AttributeServicesLoaderTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\Routing\Loader\AttributeServicesLoader; +use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\ActionPathController; +use Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures\MethodActionControllers; +use Symfony\Component\Routing\Tests\Fixtures\TraceableAttributeClassLoader; + +class AttributeServicesLoaderTest extends TestCase +{ + public function testSupports() + { + $loader = new AttributeServicesLoader(); + + $this->assertFalse($loader->supports('attributes', null)); + $this->assertFalse($loader->supports('attributes', 'attribute')); + $this->assertFalse($loader->supports('other', 'tagged_services')); + $this->assertTrue($loader->supports('attributes', 'tagged_services')); + } + + public function testDelegatesToAttributeLoaderAndMergesCollections() + { + $attributeLoader = new TraceableAttributeClassLoader(); + + $servicesLoader = new AttributeServicesLoader([ + ActionPathController::class, + MethodActionControllers::class, + ]); + + $resolver = new LoaderResolver([ + $attributeLoader, + $servicesLoader, + ]); + + $attributeLoader->setResolver($resolver); + $servicesLoader->setResolver($resolver); + + $collection = $servicesLoader->load('attributes', 'tagged_services'); + + $this->assertArrayHasKey('action', $collection->all()); + $this->assertArrayHasKey('put', $collection->all()); + $this->assertArrayHasKey('post', $collection->all()); + + $this->assertSame(['/path'], [$collection->get('action')->getPath()]); + $this->assertSame('/the/path', $collection->get('put')->getPath()); + $this->assertSame('/the/path', $collection->get('post')->getPath()); + + $this->assertSame([ + ActionPathController::class, + MethodActionControllers::class, + ], $attributeLoader->foundClasses); + } +} From d2e6f5c3f912b443d787ed31f0006dda82fcd471 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 25 Aug 2025 08:55:45 +0200 Subject: [PATCH 25/25] make RoutingControllerPass and AttributeServicesLoader final --- DependencyInjection/RoutingControllerPass.php | 2 +- Loader/AttributeServicesLoader.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DependencyInjection/RoutingControllerPass.php b/DependencyInjection/RoutingControllerPass.php index 9578faf2..4074a6e0 100644 --- a/DependencyInjection/RoutingControllerPass.php +++ b/DependencyInjection/RoutingControllerPass.php @@ -18,7 +18,7 @@ /** * @author Nicolas Grekas */ -class RoutingControllerPass implements CompilerPassInterface +final class RoutingControllerPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; diff --git a/Loader/AttributeServicesLoader.php b/Loader/AttributeServicesLoader.php index d95d9532..a3c8f493 100644 --- a/Loader/AttributeServicesLoader.php +++ b/Loader/AttributeServicesLoader.php @@ -19,7 +19,7 @@ * * @author Nicolas Grekas */ -class AttributeServicesLoader extends Loader +final class AttributeServicesLoader extends Loader { /** * @param class-string[] $taggedClasses