From 5fc670728e4ae05db0a608398bf1ba7273b72edd Mon Sep 17 00:00:00 2001 From: Santiago San Martin Date: Thu, 7 Aug 2025 18:59:46 -0300 Subject: [PATCH] [Routing] allow setting multiple envs in `#[Route]` attribute --- UPGRADE-7.4.md | 5 +++ .../Component/Routing/Attribute/Route.php | 36 +++++++++++++++++-- src/Symfony/Component/Routing/CHANGELOG.md | 1 + .../Routing/Loader/AttributeClassLoader.php | 6 ++-- .../AttributeFixtures/RouteWithEnv.php | 15 ++++++++ .../Tests/Loader/AttributeClassLoaderTest.php | 4 ++- 6 files changed, 60 insertions(+), 7 deletions(-) diff --git a/UPGRADE-7.4.md b/UPGRADE-7.4.md index 95e098365d66..d9d3b4de062c 100644 --- a/UPGRADE-7.4.md +++ b/UPGRADE-7.4.md @@ -44,6 +44,11 @@ HttpFoundation * Deprecate using `Request::sendHeaders()` after headers have already been sent; use a `StreamedResponse` instead +Routing +------- + + * Deprecate `getEnv()` and `setEnv()` methods of the `Symfony\Component\Routing\Attribute\Route` class in favor of the plurialized `getEnvs()` and `setEnvs()` methods. They get and return an array of env names + Security -------- diff --git a/src/Symfony/Component/Routing/Attribute/Route.php b/src/Symfony/Component/Routing/Attribute/Route.php index 003bbe64f5a8..1ad37e177ef3 100644 --- a/src/Symfony/Component/Routing/Attribute/Route.php +++ b/src/Symfony/Component/Routing/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/src/Symfony/Component/Routing/CHANGELOG.md b/src/Symfony/Component/Routing/CHANGELOG.md index 4ef96d53232f..3496520b323c 100644 --- a/src/Symfony/Component/Routing/CHANGELOG.md +++ b/src/Symfony/Component/Routing/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/src/Symfony/Component/Routing/Loader/AttributeClassLoader.php b/src/Symfony/Component/Routing/Loader/AttributeClassLoader.php index 254582bf3558..a5ee36ac79c4 100644 --- a/src/Symfony/Component/Routing/Loader/AttributeClassLoader.php +++ b/src/Symfony/Component/Routing/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'])) { 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())) { 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/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/RouteWithEnv.php b/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/RouteWithEnv.php index 31f6c39bca65..c8f82fdf9ee7 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures/RouteWithEnv.php +++ b/src/Symfony/Component/Routing/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/src/Symfony/Component/Routing/Tests/Loader/AttributeClassLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/AttributeClassLoaderTest.php index 2fa00e07f9f8..8ad0ede6d513 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AttributeClassLoaderTest.php +++ b/src/Symfony/Component/Routing/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()