From 0b8db47612c26f33c0b0b84994b1a3723b192f71 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 11 Sep 2026 12:43:37 +0200 Subject: [PATCH] [Routing] Add addCondition() to combine a condition with the existing ones --- .../FrameworkBundle/Tests/Fixtures/reference.php | 2 ++ src/Symfony/Component/Routing/CHANGELOG.md | 1 + .../Loader/Configurator/RoutesReference.php | 2 ++ .../Loader/Configurator/Traits/RouteTrait.php | 12 ++++++++++++ .../Routing/Loader/ContentLoaderTrait.php | 7 ++++++- .../Routing/Loader/schema/routing.schema.json | 2 ++ src/Symfony/Component/Routing/Route.php | 14 ++++++++++++++ src/Symfony/Component/Routing/RouteCollection.php | 10 ++++++++++ .../Routing/Tests/Fixtures/add_condition.yml | 15 +++++++++++++++ .../Tests/Fixtures/php_dsl_add_condition.php | 13 +++++++++++++ .../Tests/Fixtures/php_dsl_sub_add_condition.php | 10 ++++++++++ .../Routing/Tests/Loader/PhpFileLoaderTest.php | 11 +++++++++++ .../Routing/Tests/Loader/YamlFileLoaderTest.php | 11 +++++++++++ .../Routing/Tests/RouteCollectionTest.php | 14 ++++++++++++++ src/Symfony/Component/Routing/Tests/RouteTest.php | 13 +++++++++++++ 15 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/add_condition.yml create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_add_condition.php create mode 100644 src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_sub_add_condition.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/reference.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/reference.php index ee362f39bc79e..f46d2fb006622 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/reference.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/reference.php @@ -216,6 +216,7 @@ public static function config(array $config): array * host?: string|array, * schemes?: string|list, * condition?: string, + * add_condition?: string, * locale?: string, * format?: string, * utf8?: bool, @@ -236,6 +237,7 @@ public static function config(array $config): array * host?: string|array, * schemes?: string|list, * condition?: string, + * add_condition?: string, * locale?: string, * format?: string, * utf8?: bool, diff --git a/src/Symfony/Component/Routing/CHANGELOG.md b/src/Symfony/Component/Routing/CHANGELOG.md index 2af361383c9a7..4068d60f3309f 100644 --- a/src/Symfony/Component/Routing/CHANGELOG.md +++ b/src/Symfony/Component/Routing/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG * Add a `firewall` option to route definitions, exposed as the `_firewall` route default * Allow defining default query parameters with the `_query` route default * Add `RequestContext::runWith()` to generate and match URLs for another host, scheme or base URL without leaking the change + * Add `Route::addCondition()`, `RouteCollection::addCondition()` and the `add_condition` import key to combine a condition with the existing ones 8.1 --- diff --git a/src/Symfony/Component/Routing/Loader/Configurator/RoutesReference.php b/src/Symfony/Component/Routing/Loader/Configurator/RoutesReference.php index 7fd6e6a533f69..59b147fbc79f4 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/RoutesReference.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/RoutesReference.php @@ -41,6 +41,7 @@ * host?: string|array, * schemes?: string|list, * condition?: string, + * add_condition?: string, * locale?: string, * format?: string, * utf8?: bool, @@ -62,6 +63,7 @@ * host?: string|array, * schemes?: string|list, * condition?: string, + * add_condition?: string, * locale?: string, * format?: string, * utf8?: bool, diff --git a/src/Symfony/Component/Routing/Loader/Configurator/Traits/RouteTrait.php b/src/Symfony/Component/Routing/Loader/Configurator/Traits/RouteTrait.php index 3b4435faa579c..4670f8381fdfa 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/Traits/RouteTrait.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/Traits/RouteTrait.php @@ -78,6 +78,18 @@ final public function condition(string $condition): static return $this; } + /** + * Adds a condition, combined with the existing one using "and". + * + * @return $this + */ + final public function addCondition(string $condition): static + { + $this->route->addCondition($condition); + + return $this; + } + /** * Sets the pattern for the host. * diff --git a/src/Symfony/Component/Routing/Loader/ContentLoaderTrait.php b/src/Symfony/Component/Routing/Loader/ContentLoaderTrait.php index 3bf9c686db946..a099cc7a0ef2f 100644 --- a/src/Symfony/Component/Routing/Loader/ContentLoaderTrait.php +++ b/src/Symfony/Component/Routing/Loader/ContentLoaderTrait.php @@ -29,7 +29,7 @@ trait ContentLoaderTrait * Config keys accepted at the route or import level by {@see validate()}. */ private const AVAILABLE_KEYS = [ - 'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root', 'locale', 'format', 'utf8', 'exclude', 'stateless', 'firewall', + 'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'add_condition', 'controller', 'name_prefix', 'trailing_slash_on_root', 'locale', 'format', 'utf8', 'exclude', 'stateless', 'firewall', ]; /** @@ -114,6 +114,7 @@ private function parseRoute(RouteCollection $collection, string $name, array $co $routes->setSchemes($config['schemes'] ?? []); $routes->setMethods($config['methods'] ?? []); $routes->setCondition($config['condition'] ?? null); + $routes->addCondition($config['add_condition'] ?? ''); if (isset($config['host'])) { $this->addHost($routes, $config['host']); @@ -134,6 +135,7 @@ private function parseImport(RouteCollection $collection, array $config, string $options = $config['options'] ?? []; $host = $config['host'] ?? null; $condition = $config['condition'] ?? null; + $addCondition = $config['add_condition'] ?? null; $schemes = $config['schemes'] ?? null; $methods = $config['methods'] ?? null; $trailingSlashOnRoot = $config['trailing_slash_on_root'] ?? true; @@ -177,6 +179,9 @@ private function parseImport(RouteCollection $collection, array $config, string if (null !== $condition) { $subCollection->setCondition($condition); } + if (null !== $addCondition) { + $subCollection->addCondition($addCondition); + } if (null !== $schemes) { $subCollection->setSchemes($schemes); } diff --git a/src/Symfony/Component/Routing/Loader/schema/routing.schema.json b/src/Symfony/Component/Routing/Loader/schema/routing.schema.json index 983146909df28..86af02b98e732 100644 --- a/src/Symfony/Component/Routing/Loader/schema/routing.schema.json +++ b/src/Symfony/Component/Routing/Loader/schema/routing.schema.json @@ -59,6 +59,7 @@ ] }, "condition": { "type": "string" }, + "add_condition": { "type": "string" }, "locale": { "type": "string" }, "format": { "type": "string" }, "utf8": { "type": "boolean" }, @@ -128,6 +129,7 @@ ] }, "condition": { "type": "string" }, + "add_condition": { "type": "string" }, "trailing_slash_on_root": { "type": "boolean" }, "methods": { "oneOf": [ { "type": "string" }, { "type": "array", "items": { "type": "string" } } ] }, "locale": { "type": "string" }, diff --git a/src/Symfony/Component/Routing/Route.php b/src/Symfony/Component/Routing/Route.php index d01add5480abf..07b5ba658076f 100644 --- a/src/Symfony/Component/Routing/Route.php +++ b/src/Symfony/Component/Routing/Route.php @@ -382,6 +382,20 @@ public function setCondition(?string $condition): static return $this; } + /** + * Adds a condition, combined with the existing one using "and". + * + * @return $this + */ + public function addCondition(string $condition): static + { + if ('' === $condition) { + return $this; + } + + return $this->setCondition('' === $this->condition ? $condition : \sprintf('(%s) and (%s)', $this->condition, $condition)); + } + /** * Compiles the route. * diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php index 7ca86cc39801a..214cdfe632a25 100644 --- a/src/Symfony/Component/Routing/RouteCollection.php +++ b/src/Symfony/Component/Routing/RouteCollection.php @@ -266,6 +266,16 @@ public function setCondition(?string $condition): void } } + /** + * Adds a condition to all routes, combined with their existing one using "and". + */ + public function addCondition(string $condition): void + { + foreach ($this->routes as $route) { + $route->addCondition($condition); + } + } + /** * Adds defaults to all routes. * diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/add_condition.yml b/src/Symfony/Component/Routing/Tests/Fixtures/add_condition.yml new file mode 100644 index 0000000000000..f0a0fbdee9532 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/add_condition.yml @@ -0,0 +1,15 @@ +route_with_conditions: + path: /route + condition: 'context.getMethod() == "GET"' + add_condition: 'request.isSecure()' + +import_with_add_condition: + resource: validpattern.yml + name_prefix: added_ + add_condition: 'request.isSecure()' + +import_with_both_conditions: + resource: validpattern.yml + name_prefix: replaced_ + condition: 'context.getMethod() == "POST"' + add_condition: 'request.isSecure()' diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_add_condition.php b/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_add_condition.php new file mode 100644 index 0000000000000..1f6573d3294ef --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_add_condition.php @@ -0,0 +1,13 @@ +import('php_dsl_sub_add_condition.php') + ->addCondition('request.isSecure()'); + + $routes->collection('c_') + ->addCondition('request.isSecure()') + ->add('foo', '/foo') + ->addCondition('context.getMethod() == "GET"'); +}; diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_sub_add_condition.php b/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_sub_add_condition.php new file mode 100644 index 0000000000000..73900c49b1e21 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/php_dsl_sub_add_condition.php @@ -0,0 +1,10 @@ +add('with_condition', '/with-condition') + ->condition('context.getMethod() == "GET"'); + + $routes->add('without_condition', '/without-condition'); +}; diff --git a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php index 3da8265180e3b..48ab6492062e6 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php @@ -247,6 +247,17 @@ public function testRoutingConfigurator() $this->assertEquals($expectedCollectionObject, $routeCollectionObject); } + public function testRoutingConfiguratorAddCondition() + { + $locator = new FileLocator([__DIR__.'/../Fixtures']); + $loader = new PhpFileLoader($locator); + $routeCollection = $loader->load('php_dsl_add_condition.php'); + + $this->assertSame('(context.getMethod() == "GET") and (request.isSecure())', $routeCollection->get('with_condition')->getCondition()); + $this->assertSame('request.isSecure()', $routeCollection->get('without_condition')->getCondition()); + $this->assertSame('(request.isSecure()) and (context.getMethod() == "GET")', $routeCollection->get('c_foo')->getCondition()); + } + public function testRoutingConfiguratorCanImportGlobPatterns() { $locator = new FileLocator([__DIR__.'/../Fixtures/glob']); diff --git a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php index 56d3467a61f65..0e317bc328a92 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php @@ -122,6 +122,17 @@ public function testLoadWithResource() } } + public function testLoadWithAddCondition() + { + $loader = new YamlFileLoader(new FileLocator([__DIR__.'/../Fixtures'])); + $routeCollection = $loader->load('add_condition.yml'); + + $this->assertSame('(context.getMethod() == "GET") and (request.isSecure())', $routeCollection->get('route_with_conditions')->getCondition()); + $this->assertSame('(context.getMethod() == "GET") and (request.isSecure())', $routeCollection->get('added_blog_show')->getCondition()); + $this->assertSame('request.isSecure()', $routeCollection->get('added_blog_show_inherited')->getCondition()); + $this->assertSame('(context.getMethod() == "POST") and (request.isSecure())', $routeCollection->get('replaced_blog_show')->getCondition()); + } + public function testLoadRouteWithControllerAttribute() { $loader = new YamlFileLoader(new FileLocator([__DIR__.'/../Fixtures/controller'])); diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php index b4a1b5b54d8b6..d0828dab89d4b 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php @@ -265,6 +265,20 @@ public function testSetCondition() $this->assertEquals('context.getMethod() == "POST"', $routeb->getCondition()); } + public function testAddCondition() + { + $collection = new RouteCollection(); + $routea = new Route('/a'); + $routeb = new Route('/b', [], [], [], '', [], [], 'context.getMethod() == "GET"'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->addCondition('request.isSecure()'); + + $this->assertSame('request.isSecure()', $routea->getCondition()); + $this->assertSame('(context.getMethod() == "GET") and (request.isSecure())', $routeb->getCondition()); + } + public function testClone() { $collection = new RouteCollection(); diff --git a/src/Symfony/Component/Routing/Tests/RouteTest.php b/src/Symfony/Component/Routing/Tests/RouteTest.php index c7371fae57c6d..3ebccfaf4613c 100644 --- a/src/Symfony/Component/Routing/Tests/RouteTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteTest.php @@ -217,6 +217,19 @@ public function testCondition() $this->assertSame('context.getMethod() == "GET"', $route->getCondition()); } + public function testAddCondition() + { + $route = new Route('/'); + $route->addCondition('request.isSecure()'); + $this->assertSame('request.isSecure()', $route->getCondition()); + + $route->addCondition('context.getMethod() == "GET"'); + $this->assertSame('(request.isSecure()) and (context.getMethod() == "GET")', $route->getCondition()); + + $route->addCondition(''); + $this->assertSame('(request.isSecure()) and (context.getMethod() == "GET")', $route->getCondition()); + } + public function testCompile() { $route = new Route('/{foo}');