diff --git a/UPGRADE-7.4.md b/UPGRADE-7.4.md new file mode 100644 index 0000000000000..44635471d7e44 --- /dev/null +++ b/UPGRADE-7.4.md @@ -0,0 +1,14 @@ +UPGRADE FROM 7.3 to 7.4 +======================= + +Symfony 7.4 is a minor release. According to the Symfony release process, there should be no significant +backward compatibility breaks. Minor backward compatibility breaks are prefixed in this document with +`[BC BREAK]`, make sure your code is compatible with these entries before upgrading. +Read more about this in the [Symfony documentation](https://symfony.com/doc/7.4/setup/upgrade_minor.html). + +If you're upgrading from a version below 7.3, follow the [7.3 upgrade guide](UPGRADE-7.3.md) first. + +Routing +------- + +* Deprecate `RouteCollection` overriding its routes' properties diff --git a/src/Symfony/Component/Console/Debug/CliRequest.php b/src/Symfony/Component/Console/Debug/CliRequest.php index b023db07af95e..6e2c1012b16ef 100644 --- a/src/Symfony/Component/Console/Debug/CliRequest.php +++ b/src/Symfony/Component/Console/Debug/CliRequest.php @@ -24,7 +24,7 @@ public function __construct( public readonly TraceableCommand $command, ) { parent::__construct( - attributes: ['_controller' => \get_class($command->command), '_virtual_type' => 'command'], + attributes: ['_controller' => $command->command::class, '_virtual_type' => 'command'], server: $_SERVER, ); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php index 812b47c7a6f1f..7218db6deddc0 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ServiceLocatorTagPassTest.php @@ -83,7 +83,7 @@ public function testProcessValue() $this->assertSame(CustomDefinition::class, $locator('bar')::class); $this->assertSame(CustomDefinition::class, $locator('baz')::class); $this->assertSame(CustomDefinition::class, $locator('some.service')::class); - $this->assertSame(CustomDefinition::class, \get_class($locator('inlines.service'))); + $this->assertSame(CustomDefinition::class, $locator('inlines.service')::class); } public function testServiceWithKeyOverwritesPreviousInheritedKey() diff --git a/src/Symfony/Component/Routing/CHANGELOG.md b/src/Symfony/Component/Routing/CHANGELOG.md index d21e550f9b57f..f8b4ed2f296c3 100644 --- a/src/Symfony/Component/Routing/CHANGELOG.md +++ b/src/Symfony/Component/Routing/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.4 +--- + +* Deprecate `RouteCollection` overriding its routes' properties + 7.3 --- diff --git a/src/Symfony/Component/Routing/RouteCollection.php b/src/Symfony/Component/Routing/RouteCollection.php index 87e389854c38b..4c0cef7154e7e 100644 --- a/src/Symfony/Component/Routing/RouteCollection.php +++ b/src/Symfony/Component/Routing/RouteCollection.php @@ -242,10 +242,15 @@ public function addNamePrefix(string $prefix): void public function setHost(?string $pattern, array $defaults = [], array $requirements = []): void { foreach ($this->routes as $route) { + if ('' !== $route->getHost() && $route->getHost() !== $pattern) { + trigger_deprecation('symfony/routing', '7.3', 'Overriding a route\'s host with its group\'s is deprecated, you should remove it from the route.'); + } + $route->setHost($pattern); - $route->addDefaults($defaults); - $route->addRequirements($requirements); } + + $this->addDefaults($defaults); + $this->addRequirements($requirements); } /** @@ -256,6 +261,10 @@ public function setHost(?string $pattern, array $defaults = [], array $requireme public function setCondition(?string $condition): void { foreach ($this->routes as $route) { + if ('' !== $route->getCondition() && $route->getCondition() !== $condition) { + trigger_deprecation('symfony/routing', '7.3', 'Overriding a route\'s condition with its group\'s is deprecated, you should remove it from the route.'); + } + $route->setCondition($condition); } } @@ -267,10 +276,18 @@ public function setCondition(?string $condition): void */ public function addDefaults(array $defaults): void { - if ($defaults) { - foreach ($this->routes as $route) { - $route->addDefaults($defaults); + if (!$defaults) { + return; + } + + foreach ($this->routes as $route) { + foreach ($defaults as $name => $value) { + if ($route->hasDefault($name) && $route->getDefault($name) !== $value) { + trigger_deprecation('symfony/routing', '7.3', 'Overriding a route\'s default with its group\'s is deprecated, you should remove it from the route.'); + } } + + $route->addDefaults($defaults); } } @@ -281,10 +298,18 @@ public function addDefaults(array $defaults): void */ public function addRequirements(array $requirements): void { - if ($requirements) { - foreach ($this->routes as $route) { - $route->addRequirements($requirements); + if (!$requirements) { + return; + } + + foreach ($this->routes as $route) { + foreach ($requirements as $key => $regex) { + if ($route->hasRequirement($key) && $route->getRequirement($key) !== $regex) { + trigger_deprecation('symfony/routing', '7.3', 'Overriding a route\'s requirement with its group\'s is deprecated, you should remove it from the route.'); + } } + + $route->addRequirements($requirements); } } @@ -295,10 +320,18 @@ public function addRequirements(array $requirements): void */ public function addOptions(array $options): void { - if ($options) { - foreach ($this->routes as $route) { - $route->addOptions($options); + if (!$options) { + return; + } + + foreach ($this->routes as $route) { + foreach ($options as $name => $value) { + if ($route->hasOption($name) && $route->getOption($name) !== $value) { + trigger_deprecation('symfony/routing', '7.3', 'Overriding a route\'s option with its group\'s is deprecated, you should remove it from the route.'); + } } + + $route->addOptions($options); } } @@ -310,6 +343,10 @@ public function addOptions(array $options): void public function setSchemes(string|array $schemes): void { foreach ($this->routes as $route) { + if ($route->getSchemes() !== [] && $route->getSchemes() != (array) $schemes) { + trigger_deprecation('symfony/routing', '7.3', 'Overriding a route\'s schemes with its group\'s is deprecated, you should remove it from the route.'); + } + $route->setSchemes($schemes); } } @@ -322,6 +359,10 @@ public function setSchemes(string|array $schemes): void public function setMethods(string|array $methods): void { foreach ($this->routes as $route) { + if ($route->getMethods() !== [] && $route->getMethods() != (array) $methods) { + trigger_deprecation('symfony/routing', '7.3', 'Overriding a route\'s methods with its group\'s is deprecated, you should remove it from the route.'); + } + $route->setMethods($methods); } } diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php index 7625bcf54ebdb..40f29c73b54e6 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCollectionTest.php @@ -12,12 +12,15 @@ namespace Symfony\Component\Routing\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectUserDeprecationMessageTrait; use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; class RouteCollectionTest extends TestCase { + use ExpectUserDeprecationMessageTrait; + public function testRoute() { $collection = new RouteCollection(); @@ -115,27 +118,65 @@ public function testAddCollectionWithResources() public function testAddDefaultsAndRequirementsAndOptions() { $collection = new RouteCollection(); - $collection->add('foo', new Route('/{placeholder}')); $collection1 = new RouteCollection(); - $collection1->add('bar', new Route('/{placeholder}', - ['_controller' => 'fixed', 'placeholder' => 'default'], ['placeholder' => '.+'], ['option' => 'value']) - ); + $collection1->add('foo', new Route('/{placeholder}')); $collection->addCollection($collection1); $collection->addDefaults(['placeholder' => 'new-default']); $this->assertEquals(['placeholder' => 'new-default'], $collection->get('foo')->getDefaults(), '->addDefaults() adds defaults to all routes'); - $this->assertEquals(['_controller' => 'fixed', 'placeholder' => 'new-default'], $collection->get('bar')->getDefaults(), - '->addDefaults() adds defaults to all routes and overwrites existing ones'); $collection->addRequirements(['placeholder' => '\d+']); $this->assertEquals(['placeholder' => '\d+'], $collection->get('foo')->getRequirements(), '->addRequirements() adds requirements to all routes'); - $this->assertEquals(['placeholder' => '\d+'], $collection->get('bar')->getRequirements(), - '->addRequirements() adds requirements to all routes and overwrites existing ones'); $collection->addOptions(['option' => 'new-value']); $this->assertEquals( ['option' => 'new-value', 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler'], - $collection->get('bar')->getOptions(), '->addOptions() adds options to all routes and overwrites existing ones' + $collection->get('foo')->getOptions(), '->addOptions() adds options to all routes' + ); + } + + /** + * @group legacy + */ + public function testCollectionOverridingDefaultsIsDeprecated() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{placeholder}', ['placeholder' => 'default'])); + + $this->expectUserDeprecationMessage('Since symfony/routing 7.3: Overriding a route\'s default with its group\'s is deprecated, you should remove it from the route.'); + $collection->addDefaults(['placeholder' => 'new-default']); + + $this->assertEquals(['placeholder' => 'new-default'], $collection->get('foo')->getDefaults()); + } + + /** + * @group legacy + */ + public function testCollectionOverridingRequirementsIsDeprecated() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{placeholder}', requirements: ['placeholder' => '.+'])); + + $this->expectUserDeprecationMessage('Since symfony/routing 7.3: Overriding a route\'s requirement with its group\'s is deprecated, you should remove it from the route.'); + $collection->addRequirements(['placeholder' => '\d+']); + + $this->assertEquals(['placeholder' => '\d+'], $collection->get('foo')->getRequirements()); + } + + /** + * @group legacy + */ + public function testCollectionOverridingOptionsIsDeprecated() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{placeholder}', options: ['option' => 'value'])); + + $this->expectUserDeprecationMessage('Since symfony/routing 7.3: Overriding a route\'s option with its group\'s is deprecated, you should remove it from the route.'); + $collection->addOptions(['option' => 'new-value']); + + $this->assertEquals( + ['option' => 'new-value', 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler'], + $collection->get('foo')->getOptions() ); } @@ -240,29 +281,52 @@ public function testRemove() public function testSetHost() { $collection = new RouteCollection(); - $routea = new Route('/a'); - $routeb = new Route('/b', [], [], [], '{locale}.example.net'); - $collection->add('a', $routea); - $collection->add('b', $routeb); + $route = new Route('/a'); + $collection->add('a', $route); $collection->setHost('{locale}.example.com'); - $this->assertEquals('{locale}.example.com', $routea->getHost()); - $this->assertEquals('{locale}.example.com', $routeb->getHost()); + $this->assertEquals('{locale}.example.com', $route->getHost()); + } + + /** + * @group legacy + */ + public function testCollectionOverridingHostIsDeprecated() + { + $collection = new RouteCollection(); + $route = new Route('/a', [], [], [], '{locale}.example.net'); + $collection->add('a', $route); + + $this->expectUserDeprecationMessage('Since symfony/routing 7.3: Overriding a route\'s host with its group\'s is deprecated, you should remove it from the route.'); + $collection->setHost('{locale}.example.com'); + + $this->assertEquals('{locale}.example.com', $route->getHost()); } public function testSetCondition() { $collection = new RouteCollection(); $routea = new Route('/a'); - $routeb = new Route('/b', [], [], [], '{locale}.example.net', [], [], 'context.getMethod() == "GET"'); $collection->add('a', $routea); - $collection->add('b', $routeb); $collection->setCondition('context.getMethod() == "POST"'); $this->assertEquals('context.getMethod() == "POST"', $routea->getCondition()); - $this->assertEquals('context.getMethod() == "POST"', $routeb->getCondition()); + } + + /** + * @group legacy + */ + public function testCollectionOverridingConditionsIsDeprecated() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{placeholder}', condition: 'condition')); + + $this->expectUserDeprecationMessage('Since symfony/routing 7.3: Overriding a route\'s condition with its group\'s is deprecated, you should remove it from the route.'); + $collection->setCondition(null); + + $this->assertEquals('', $collection->get('foo')->getCondition()); } public function testClone() @@ -283,29 +347,53 @@ public function testClone() public function testSetSchemes() { $collection = new RouteCollection(); - $routea = new Route('/a', [], [], [], '', 'http'); - $routeb = new Route('/b'); - $collection->add('a', $routea); - $collection->add('b', $routeb); + $route = new Route('/a'); + $collection->add('a', $route); + + $collection->setSchemes(['http', 'https']); + + $this->assertEquals(['http', 'https'], $route->getSchemes()); + } + /** + * @group legacy + */ + public function testCollectionOverridingSchemesIsDeprecated() + { + $collection = new RouteCollection(); + $route = new Route('/a', [], [], [], '', 'http'); + $collection->add('a', $route); + + $this->expectUserDeprecationMessage('Since symfony/routing 7.3: Overriding a route\'s schemes with its group\'s is deprecated, you should remove it from the route.'); $collection->setSchemes(['http', 'https']); - $this->assertEquals(['http', 'https'], $routea->getSchemes()); - $this->assertEquals(['http', 'https'], $routeb->getSchemes()); + $this->assertEquals(['http', 'https'], $route->getSchemes()); } public function testSetMethods() { $collection = new RouteCollection(); - $routea = new Route('/a', [], [], [], '', [], ['GET', 'POST']); - $routeb = new Route('/b'); - $collection->add('a', $routea); - $collection->add('b', $routeb); + $route = new Route('/a'); + $collection->add('a', $route); + + $collection->setMethods('PUT'); + + $this->assertEquals(['PUT'], $route->getMethods()); + } + + /** + * @group legacy + */ + public function testCollectionOverridingMethodsIsDeprecated() + { + $collection = new RouteCollection(); + $route = new Route('/a', [], [], [], '', [], ['GET', 'POST']); + $collection->add('a', $route); + $this->expectUserDeprecationMessage('Since symfony/routing 7.3: Overriding a route\'s methods with its group\'s is deprecated, you should remove it from the route.'); $collection->setMethods('PUT'); - $this->assertEquals(['PUT'], $routea->getMethods()); - $this->assertEquals(['PUT'], $routeb->getMethods()); + $this->assertEquals(['PUT'], $route->getMethods()); } public function testAddNamePrefix()