From 2b14679503a0388253b988cbc7e6f0314494de15 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 24 Jan 2023 15:02:24 +0100 Subject: [PATCH 01/19] Update license years (last time) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 008370457..0138f8f07 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2023 Fabien Potencier +Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From e2e2954a51e849e19d20e8761648568280ff7681 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 27 Jan 2023 11:03:16 +0100 Subject: [PATCH 02/19] [DependencyInjection] Fix combinatory explosion when autowiring union and intersection types --- Compiler/AutowirePass.php | 63 ++++++++++++++------------------------- 1 file changed, 23 insertions(+), 40 deletions(-) diff --git a/Compiler/AutowirePass.php b/Compiler/AutowirePass.php index c2b80770c..81c4b11e1 100644 --- a/Compiler/AutowirePass.php +++ b/Compiler/AutowirePass.php @@ -45,7 +45,6 @@ class AutowirePass extends AbstractRecursivePass private $decoratedMethodIndex; private $decoratedMethodArgumentIndex; private $typesClone; - private $combinedAliases; public function __construct(bool $throwOnAutowireException = true) { @@ -61,8 +60,6 @@ public function __construct(bool $throwOnAutowireException = true) */ public function process(ContainerBuilder $container) { - $this->populateCombinedAliases($container); - try { $this->typesClone = clone $this; parent::process($container); @@ -75,7 +72,6 @@ public function process(ContainerBuilder $container) $this->decoratedMethodIndex = null; $this->decoratedMethodArgumentIndex = null; $this->typesClone = null; - $this->combinedAliases = []; } } @@ -371,12 +367,12 @@ private function getAutowiredReference(TypedReference $reference, bool $filterTy return new TypedReference($alias, $type, $reference->getInvalidBehavior()); } - if (null !== ($alias = $this->combinedAliases[$alias] ?? null) && !$this->container->findDefinition($alias)->isAbstract()) { + if (null !== ($alias = $this->getCombinedAlias($type, $name) ?? null) && !$this->container->findDefinition($alias)->isAbstract()) { return new TypedReference($alias, $type, $reference->getInvalidBehavior()); } if ($this->container->has($name) && !$this->container->findDefinition($name)->isAbstract()) { - foreach ($this->container->getAliases() + $this->combinedAliases as $id => $alias) { + foreach ($this->container->getAliases() as $id => $alias) { if ($name === (string) $alias && str_starts_with($id, $type.' $')) { return new TypedReference($name, $type, $reference->getInvalidBehavior()); } @@ -388,7 +384,7 @@ private function getAutowiredReference(TypedReference $reference, bool $filterTy return new TypedReference($type, $type, $reference->getInvalidBehavior()); } - if (null !== ($alias = $this->combinedAliases[$type] ?? null) && !$this->container->findDefinition($alias)->isAbstract()) { + if (null !== ($alias = $this->getCombinedAlias($type) ?? null) && !$this->container->findDefinition($alias)->isAbstract()) { return new TypedReference($alias, $type, $reference->getInvalidBehavior()); } @@ -582,44 +578,31 @@ private function populateAutowiringAlias(string $id): void } } - private function populateCombinedAliases(ContainerBuilder $container): void + private function getCombinedAlias(string $type, string $name = null): ?string { - $this->combinedAliases = []; - $reverseAliases = []; - - foreach ($container->getAliases() as $id => $alias) { - if (!preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^((?&V)(?:\\\\(?&V))*+)(?: \$((?&V)))?$/', $id, $m)) { - continue; - } - - $type = $m[2]; - $name = $m[3] ?? ''; - $reverseAliases[(string) $alias][$name][] = $type; + if (str_contains($type, '&')) { + $types = explode('&', $type); + } elseif (str_contains($type, '|')) { + $types = explode('|', $type); + } else { + return null; } - foreach ($reverseAliases as $alias => $names) { - foreach ($names as $name => $types) { - if (2 > $count = \count($types)) { - continue; - } - sort($types); - $i = 1 << $count; - - // compute the powerset of the list of types - while ($i--) { - $set = []; - for ($j = 0; $j < $count; ++$j) { - if ($i & (1 << $j)) { - $set[] = $types[$j]; - } - } + $alias = null; + $suffix = $name ? ' $'.$name : ''; - if (2 <= \count($set)) { - $this->combinedAliases[implode('&', $set).('' === $name ? '' : ' $'.$name)] = $alias; - $this->combinedAliases[implode('|', $set).('' === $name ? '' : ' $'.$name)] = $alias; - } - } + foreach ($types as $type) { + if (!$this->container->hasAlias($type.$suffix)) { + return null; + } + + if (null === $alias) { + $alias = (string) $this->container->getAlias($type.$suffix); + } elseif ((string) $this->container->getAlias($type.$suffix) !== $alias) { + return null; } } + + return $alias; } } From 891565a57a767459429c6837a4ba54b8b3c0575d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 7 Feb 2023 16:02:15 +0100 Subject: [PATCH 03/19] [VarExporter] Fix lazy-proxying readonly classes on PHP 8.3 --- Tests/Fixtures/php/services_dedup_lazy.php | 5 +---- Tests/Fixtures/php/services_wither_lazy.php | 2 -- composer.json | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Tests/Fixtures/php/services_dedup_lazy.php b/Tests/Fixtures/php/services_dedup_lazy.php index 867cda7e7..204885ae0 100644 --- a/Tests/Fixtures/php/services_dedup_lazy.php +++ b/Tests/Fixtures/php/services_dedup_lazy.php @@ -117,10 +117,7 @@ class stdClassProxy5a8a5eb extends \stdClass implements \Symfony\Component\VarEx { use \Symfony\Component\VarExporter\LazyProxyTrait; - private const LAZY_OBJECT_PROPERTY_SCOPES = [ - 'lazyObjectReal' => [self::class, 'lazyObjectReal', null], - "\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', null], - ]; + private const LAZY_OBJECT_PROPERTY_SCOPES = []; } // Help opcache.preload discover always-needed symbols diff --git a/Tests/Fixtures/php/services_wither_lazy.php b/Tests/Fixtures/php/services_wither_lazy.php index b0bbc7b64..213d4fd54 100644 --- a/Tests/Fixtures/php/services_wither_lazy.php +++ b/Tests/Fixtures/php/services_wither_lazy.php @@ -76,8 +76,6 @@ class WitherProxy94fa281 extends \Symfony\Component\DependencyInjection\Tests\Co use \Symfony\Component\VarExporter\LazyProxyTrait; private const LAZY_OBJECT_PROPERTY_SCOPES = [ - 'lazyObjectReal' => [self::class, 'lazyObjectReal', null], - "\0".self::class."\0lazyObjectReal" => [self::class, 'lazyObjectReal', null], 'foo' => [parent::class, 'foo', null], ]; } diff --git a/composer.json b/composer.json index baaef5da8..70cbffc2e 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "psr/container": "^1.1|^2.0", "symfony/deprecation-contracts": "^2.1|^3", "symfony/service-contracts": "^1.1.6|^2.0|^3.0", - "symfony/var-exporter": "^6.2" + "symfony/var-exporter": "^6.2.7" }, "require-dev": { "symfony/yaml": "^5.4|^6.0", From 51796599e637723ae52eeda3b32be5bf11f9ac9f Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Sat, 11 Feb 2023 07:55:39 -0500 Subject: [PATCH 04/19] [DI] keep `proxy` tag on original definition when decorating --- Compiler/DecoratorServicePass.php | 2 +- Tests/Compiler/DecoratorServicePassTest.php | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Compiler/DecoratorServicePass.php b/Compiler/DecoratorServicePass.php index 6b7a034d6..8ca86c111 100644 --- a/Compiler/DecoratorServicePass.php +++ b/Compiler/DecoratorServicePass.php @@ -53,7 +53,7 @@ public function process(ContainerBuilder $container) $tagsToKeep = $container->hasParameter('container.behavior_describing_tags') ? $container->getParameter('container.behavior_describing_tags') - : ['container.do_not_inline', 'container.service_locator', 'container.service_subscriber', 'container.service_subscriber.locator']; + : ['proxy', 'container.do_not_inline', 'container.service_locator', 'container.service_subscriber', 'container.service_subscriber.locator']; foreach ($definitions as [$id, $definition]) { $decoratedService = $definition->getDecoratedService(); diff --git a/Tests/Compiler/DecoratorServicePassTest.php b/Tests/Compiler/DecoratorServicePassTest.php index 7f548492b..cac046084 100644 --- a/Tests/Compiler/DecoratorServicePassTest.php +++ b/Tests/Compiler/DecoratorServicePassTest.php @@ -262,6 +262,25 @@ public function testProcessLeavesServiceSubscriberTagOnOriginalDefinition() $this->assertEquals(['bar' => ['attr' => 'baz'], 'foobar' => ['attr' => 'bar']], $container->getDefinition('baz')->getTags()); } + public function testProcessLeavesProxyTagOnOriginalDefinition() + { + $container = new ContainerBuilder(); + $container + ->register('foo') + ->setTags(['proxy' => 'foo', 'bar' => ['attr' => 'baz']]) + ; + $container + ->register('baz') + ->setTags(['foobar' => ['attr' => 'bar']]) + ->setDecoratedService('foo') + ; + + $this->process($container); + + $this->assertEquals(['proxy' => 'foo'], $container->getDefinition('baz.inner')->getTags()); + $this->assertEquals(['bar' => ['attr' => 'baz'], 'foobar' => ['attr' => 'bar']], $container->getDefinition('baz')->getTags()); + } + public function testCannotDecorateSyntheticService() { $container = new ContainerBuilder(); From bed0998389e5cd234bdb9f9d3905943c4d6894bc Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Wed, 14 Dec 2022 15:42:16 +0100 Subject: [PATCH 05/19] Migrate to `static` data providers using `rector/rector` --- Tests/AliasTest.php | 2 +- Tests/Argument/TaggedIteratorArgumentTest.php | 4 +-- Tests/ChildDefinitionTest.php | 2 +- .../AliasDeprecatedPublicServicesPassTest.php | 2 +- Tests/Compiler/AutowirePassTest.php | 2 +- .../CheckArgumentsValidityPassTest.php | 2 +- Tests/Compiler/IntegrationTest.php | 2 +- .../PriorityTaggedServiceTraitTest.php | 2 +- Tests/Compiler/ResolveClassPassTest.php | 4 +-- .../Compiler/ResolveFactoryClassPassTest.php | 2 +- Tests/ContainerBuilderTest.php | 4 +-- Tests/ContainerTest.php | 4 +-- Tests/CrossCheckTest.php | 2 +- Tests/DefinitionTest.php | 2 +- Tests/Dumper/PhpDumperTest.php | 4 +-- Tests/Dumper/XmlDumperTest.php | 4 +-- Tests/EnvVarProcessorTest.php | 36 +++++++++---------- .../InvalidParameterTypeExceptionTest.php | 2 +- Tests/Extension/ExtensionTest.php | 2 +- .../Configurator/EnvConfiguratorTest.php | 2 +- Tests/Loader/FileLoaderTest.php | 2 +- Tests/Loader/IniFileLoaderTest.php | 2 +- Tests/Loader/LoaderResolverTest.php | 2 +- Tests/Loader/PhpFileLoaderTest.php | 2 +- Tests/Loader/YamlFileLoaderTest.php | 2 +- Tests/ParameterBag/ParameterBagTest.php | 4 +-- 26 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Tests/AliasTest.php b/Tests/AliasTest.php index e2e6f0a0b..7d5521ddb 100644 --- a/Tests/AliasTest.php +++ b/Tests/AliasTest.php @@ -128,7 +128,7 @@ public function testCannotDeprecateWithAnInvalidTemplate($message) $def->setDeprecated('package', '1.1', $message); } - public function invalidDeprecationMessageProvider() + public static function invalidDeprecationMessageProvider() { return [ "With \rs" => ["invalid \r message %alias_id%"], diff --git a/Tests/Argument/TaggedIteratorArgumentTest.php b/Tests/Argument/TaggedIteratorArgumentTest.php index fbeb0238a..dcc38dde7 100644 --- a/Tests/Argument/TaggedIteratorArgumentTest.php +++ b/Tests/Argument/TaggedIteratorArgumentTest.php @@ -67,7 +67,7 @@ public function testDefaultIndexMethod(?string $indexAttribute, ?string $default $this->assertSame($expectedDefaultIndexMethod, $taggedIteratorArgument->getDefaultIndexMethod()); } - public function defaultIndexMethodProvider() + public static function defaultIndexMethodProvider() { yield 'No indexAttribute and no defaultIndexMethod' => [ null, @@ -116,7 +116,7 @@ public function testDefaultPriorityIndexMethod(?string $indexAttribute, ?string $this->assertSame($expectedDefaultPriorityMethod, $taggedIteratorArgument->getDefaultPriorityMethod()); } - public function defaultPriorityMethodProvider() + public static function defaultPriorityMethodProvider() { yield 'No indexAttribute and no defaultPriorityMethod' => [ null, diff --git a/Tests/ChildDefinitionTest.php b/Tests/ChildDefinitionTest.php index 5c7e18759..8340c3e63 100644 --- a/Tests/ChildDefinitionTest.php +++ b/Tests/ChildDefinitionTest.php @@ -40,7 +40,7 @@ public function testSetProperty($property, $changeKey) $this->assertSame([$changeKey => true], $def->getChanges()); } - public function getPropertyTests() + public static function getPropertyTests() { return [ ['class', 'class'], diff --git a/Tests/Compiler/AliasDeprecatedPublicServicesPassTest.php b/Tests/Compiler/AliasDeprecatedPublicServicesPassTest.php index 406beebce..9baff5e6f 100644 --- a/Tests/Compiler/AliasDeprecatedPublicServicesPassTest.php +++ b/Tests/Compiler/AliasDeprecatedPublicServicesPassTest.php @@ -58,7 +58,7 @@ public function testProcessWithMissingAttribute(string $attribute, array $attrib (new AliasDeprecatedPublicServicesPass())->process($container); } - public function processWithMissingAttributeProvider() + public static function processWithMissingAttributeProvider() { return [ ['package', ['version' => '1.2']], diff --git a/Tests/Compiler/AutowirePassTest.php b/Tests/Compiler/AutowirePassTest.php index 6dcc9d220..e61e2d45f 100644 --- a/Tests/Compiler/AutowirePassTest.php +++ b/Tests/Compiler/AutowirePassTest.php @@ -954,7 +954,7 @@ public function testNotWireableCalls($method, $expectedMsg) } } - public function provideNotWireableCalls() + public static function provideNotWireableCalls() { return [ ['setNotAutowireable', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class was not found.'], diff --git a/Tests/Compiler/CheckArgumentsValidityPassTest.php b/Tests/Compiler/CheckArgumentsValidityPassTest.php index 322dcd258..6d0a2edd0 100644 --- a/Tests/Compiler/CheckArgumentsValidityPassTest.php +++ b/Tests/Compiler/CheckArgumentsValidityPassTest.php @@ -56,7 +56,7 @@ public function testException(array $arguments, array $methodCalls) $pass->process($container); } - public function definitionProvider() + public static function definitionProvider() { return [ [['a' => 'a', null], []], diff --git a/Tests/Compiler/IntegrationTest.php b/Tests/Compiler/IntegrationTest.php index 0c1fe4d14..fae877219 100644 --- a/Tests/Compiler/IntegrationTest.php +++ b/Tests/Compiler/IntegrationTest.php @@ -264,7 +264,7 @@ public function testYamlContainerCompiles($directory, $actualServiceId, $expecte $this->assertEquals($expectedService, $actualService); } - public function getYamlCompileTests() + public static function getYamlCompileTests() { $container = new ContainerBuilder(); $container->registerForAutoconfiguration(IntegrationTestStub::class); diff --git a/Tests/Compiler/PriorityTaggedServiceTraitTest.php b/Tests/Compiler/PriorityTaggedServiceTraitTest.php index 4f4b2a694..4d5ee1fb4 100644 --- a/Tests/Compiler/PriorityTaggedServiceTraitTest.php +++ b/Tests/Compiler/PriorityTaggedServiceTraitTest.php @@ -182,7 +182,7 @@ public function testTheIndexedTagsByDefaultIndexMethodFailure(string $defaultInd $priorityTaggedServiceTraitImplementation->test($tag, $container); } - public function provideInvalidDefaultMethods(): iterable + public static function provideInvalidDefaultMethods(): iterable { yield ['getMethodShouldBeStatic', null, sprintf('Method "%s::getMethodShouldBeStatic()" should be static.', FooTaggedForInvalidDefaultMethodClass::class)]; yield ['getMethodShouldBeStatic', 'foo', sprintf('Either method "%s::getMethodShouldBeStatic()" should be static or tag "my_custom_tag" on service "service1" is missing attribute "foo".', FooTaggedForInvalidDefaultMethodClass::class)]; diff --git a/Tests/Compiler/ResolveClassPassTest.php b/Tests/Compiler/ResolveClassPassTest.php index 89e5fa2ea..6678b6c5d 100644 --- a/Tests/Compiler/ResolveClassPassTest.php +++ b/Tests/Compiler/ResolveClassPassTest.php @@ -33,7 +33,7 @@ public function testResolveClassFromId($serviceId) $this->assertSame($serviceId, $def->getClass()); } - public function provideValidClassId() + public static function provideValidClassId() { yield ['Acme\UnknownClass']; yield [CaseSensitiveClass::class]; @@ -52,7 +52,7 @@ public function testWontResolveClassFromId($serviceId) $this->assertNull($def->getClass()); } - public function provideInvalidClassId() + public static function provideInvalidClassId() { yield [\stdClass::class]; yield ['bar']; diff --git a/Tests/Compiler/ResolveFactoryClassPassTest.php b/Tests/Compiler/ResolveFactoryClassPassTest.php index 6b69e0bf2..8d93eeb9c 100644 --- a/Tests/Compiler/ResolveFactoryClassPassTest.php +++ b/Tests/Compiler/ResolveFactoryClassPassTest.php @@ -46,7 +46,7 @@ public function testInlinedDefinitionFactoryIsProcessed() $this->assertSame(['Baz\Qux', 'getInstance'], $factory->getFactory()[0]->getFactory()); } - public function provideFulfilledFactories() + public static function provideFulfilledFactories() { return [ [['Foo\Bar', 'create']], diff --git a/Tests/ContainerBuilderTest.php b/Tests/ContainerBuilderTest.php index 5fa9c5dfb..bf35ba135 100644 --- a/Tests/ContainerBuilderTest.php +++ b/Tests/ContainerBuilderTest.php @@ -227,7 +227,7 @@ public function testBadDefinitionId($id) $builder->setDefinition($id, new Definition('Foo')); } - public function provideBadId() + public static function provideBadId() { return [ [''], @@ -1490,7 +1490,7 @@ public function testAlmostCircular($visibility) $this->assertInstanceOf(\stdClass::class, $listener4); } - public function provideAlmostCircular() + public static function provideAlmostCircular() { yield ['public']; yield ['private']; diff --git a/Tests/ContainerTest.php b/Tests/ContainerTest.php index 497dc9938..d9fa0f379 100644 --- a/Tests/ContainerTest.php +++ b/Tests/ContainerTest.php @@ -40,7 +40,7 @@ public function testCamelize($id, $expected) $this->assertEquals($expected, Container::camelize($id), sprintf('Container::camelize("%s")', $id)); } - public function dataForTestCamelize() + public static function dataForTestCamelize() { return [ ['foo_bar', 'FooBar'], @@ -64,7 +64,7 @@ public function testUnderscore($id, $expected) $this->assertEquals($expected, Container::underscore($id), sprintf('Container::underscore("%s")', $id)); } - public function dataForTestUnderscore() + public static function dataForTestUnderscore() { return [ ['FooBar', 'foo_bar'], diff --git a/Tests/CrossCheckTest.php b/Tests/CrossCheckTest.php index 2b0a13016..699080d6b 100644 --- a/Tests/CrossCheckTest.php +++ b/Tests/CrossCheckTest.php @@ -71,7 +71,7 @@ public function testCrossCheck($fixture, $type) $this->assertEquals($services2, $services1, 'Iterator on the containers returns the same services'); } - public function crossCheckLoadersDumpers() + public static function crossCheckLoadersDumpers() { return [ ['services1.xml', 'xml'], diff --git a/Tests/DefinitionTest.php b/Tests/DefinitionTest.php index ae6d5bd94..73bae6059 100644 --- a/Tests/DefinitionTest.php +++ b/Tests/DefinitionTest.php @@ -213,7 +213,7 @@ public function testSetDeprecatedWithInvalidDeprecationTemplate($message) $def->setDeprecated('vendor/package', '1.1', $message); } - public function invalidDeprecationMessageProvider() + public static function invalidDeprecationMessageProvider() { return [ "With \rs" => ["invalid \r message %service_id%"], diff --git a/Tests/Dumper/PhpDumperTest.php b/Tests/Dumper/PhpDumperTest.php index 4c05bc49a..9763d2c15 100644 --- a/Tests/Dumper/PhpDumperTest.php +++ b/Tests/Dumper/PhpDumperTest.php @@ -398,7 +398,7 @@ public function testInvalidFactories($factory) $dumper->dump(); } - public function provideInvalidFactories() + public static function provideInvalidFactories() { return [ [['', 'method']], @@ -1105,7 +1105,7 @@ public function testAlmostCircular($visibility) $this->assertInstanceOf(\stdClass::class, $listener4); } - public function provideAlmostCircular() + public static function provideAlmostCircular() { yield ['public']; yield ['private']; diff --git a/Tests/Dumper/XmlDumperTest.php b/Tests/Dumper/XmlDumperTest.php index 4149198fa..9e2547cc2 100644 --- a/Tests/Dumper/XmlDumperTest.php +++ b/Tests/Dumper/XmlDumperTest.php @@ -127,7 +127,7 @@ public function testDumpDecoratedServices($expectedXmlDump, $container) $this->assertEquals($expectedXmlDump, $dumper->dump()); } - public function provideDecoratedServicesData() + public static function provideDecoratedServicesData() { $fixturesPath = realpath(__DIR__.'/../Fixtures/'); @@ -191,7 +191,7 @@ public function testCompiledContainerCanBeDumped($containerFile) $this->addToAssertionCount(1); } - public function provideCompiledContainerData() + public static function provideCompiledContainerData() { return [ ['container8'], diff --git a/Tests/EnvVarProcessorTest.php b/Tests/EnvVarProcessorTest.php index 248f6313b..e14fb6ae2 100644 --- a/Tests/EnvVarProcessorTest.php +++ b/Tests/EnvVarProcessorTest.php @@ -43,7 +43,7 @@ public function testGetEnvString($value, $processed) $this->assertSame($processed, $result); } - public function validStrings() + public static function validStrings() { return [ ['hello', 'hello'], @@ -89,7 +89,7 @@ public function testGetEnvNot($value, $processed) $this->assertSame(!$processed, $result); } - public function validBools() + public static function validBools() { return [ ['true', true], @@ -118,7 +118,7 @@ public function testGetEnvInt($value, $processed) $this->assertSame($processed, $result); } - public function validInts() + public static function validInts() { return [ ['1', 1], @@ -143,7 +143,7 @@ public function testGetEnvIntInvalid($value) }); } - public function invalidInts() + public static function invalidInts() { return [ ['foo'], @@ -168,7 +168,7 @@ public function testGetEnvFloat($value, $processed) $this->assertSame($processed, $result); } - public function validFloats() + public static function validFloats() { return [ ['1', 1.0], @@ -193,7 +193,7 @@ public function testGetEnvFloatInvalid($value) }); } - public function invalidFloats() + public static function invalidFloats() { return [ ['foo'], @@ -218,7 +218,7 @@ public function testGetEnvConst($value, $processed) $this->assertSame($processed, $result); } - public function validConsts() + public static function validConsts() { return [ ['Symfony\Component\DependencyInjection\Tests\EnvVarProcessorTest::TEST_CONST', self::TEST_CONST], @@ -242,7 +242,7 @@ public function testGetEnvConstInvalid($value) }); } - public function invalidConsts() + public static function invalidConsts() { return [ ['Symfony\Component\DependencyInjection\Tests\EnvVarProcessorTest::UNDEFINED_CONST'], @@ -298,7 +298,7 @@ public function testGetEnvJson($value, $processed) $this->assertSame($processed, $result); } - public function validJson() + public static function validJson() { return [ ['[1]', [1]], @@ -336,7 +336,7 @@ public function testGetEnvJsonOther($value) }); } - public function otherJsonValues() + public static function otherJsonValues() { return [ [1], @@ -387,7 +387,7 @@ public function testGetEnvKeyNoArrayResult($value) }); } - public function noArrayValues() + public static function noArrayValues() { return [ [null], @@ -413,7 +413,7 @@ public function testGetEnvKeyArrayKeyNotFound($value) }); } - public function invalidArrayValues() + public static function invalidArrayValues() { return [ [[]], @@ -436,7 +436,7 @@ public function testGetEnvKey($value) })); } - public function arrayValues() + public static function arrayValues() { return [ [['index' => 'password']], @@ -478,7 +478,7 @@ public function testGetEnvNullable($value, $processed) $this->assertSame($processed, $result); } - public function validNullables() + public static function validNullables() { return [ ['hello', 'hello'], @@ -533,7 +533,7 @@ public function testGetEnvResolve($value, $processed) $this->assertSame($processed, $result); } - public function validResolve() + public static function validResolve() { return [ ['string', 'string'], @@ -574,7 +574,7 @@ public function testGetEnvResolveNotScalar($value) }); } - public function notScalarResolve() + public static function notScalarResolve() { return [ [null], @@ -634,7 +634,7 @@ public function testGetEnvCsv($value, $processed) $this->assertSame($processed, $result); } - public function validCsv() + public static function validCsv() { $complex = <<<'CSV' ,"""","foo""","\""",\,foo\ @@ -749,7 +749,7 @@ public function testGetEnvUrlPath(?string $expected, string $url) })['path']); } - public function provideGetEnvUrlPath() + public static function provideGetEnvUrlPath() { return [ ['', 'https://symfony.com'], diff --git a/Tests/Exception/InvalidParameterTypeExceptionTest.php b/Tests/Exception/InvalidParameterTypeExceptionTest.php index d61388ea2..ef88c71ca 100644 --- a/Tests/Exception/InvalidParameterTypeExceptionTest.php +++ b/Tests/Exception/InvalidParameterTypeExceptionTest.php @@ -26,7 +26,7 @@ public function testExceptionMessage(\ReflectionParameter $parameter, string $ex self::assertSame($expectedMessage, $exception->getMessage()); } - public function provideReflectionParameters(): iterable + public static function provideReflectionParameters(): iterable { yield 'static method' => [ new \ReflectionParameter([MyClass::class, 'doSomething'], 0), diff --git a/Tests/Extension/ExtensionTest.php b/Tests/Extension/ExtensionTest.php index 37fa0ad9d..48d8b0078 100644 --- a/Tests/Extension/ExtensionTest.php +++ b/Tests/Extension/ExtensionTest.php @@ -32,7 +32,7 @@ public function testIsConfigEnabledReturnsTheResolvedValue($enabled) $this->assertSame($enabled, $extension->isConfigEnabled(new ContainerBuilder(), ['enabled' => $enabled])); } - public function getResolvedEnabledFixtures() + public static function getResolvedEnabledFixtures() { return [ [true], diff --git a/Tests/Loader/Configurator/EnvConfiguratorTest.php b/Tests/Loader/Configurator/EnvConfiguratorTest.php index 0b354e761..10f8e6f5a 100644 --- a/Tests/Loader/Configurator/EnvConfiguratorTest.php +++ b/Tests/Loader/Configurator/EnvConfiguratorTest.php @@ -24,7 +24,7 @@ public function test(string $expected, EnvConfigurator $envConfigurator) $this->assertSame($expected, (string) $envConfigurator); } - public function provide() + public static function provide() { yield ['%env(FOO)%', new EnvConfigurator('FOO')]; yield ['%env(string:FOO)%', new EnvConfigurator('string:FOO')]; diff --git a/Tests/Loader/FileLoaderTest.php b/Tests/Loader/FileLoaderTest.php index 6c2743e86..02e34fc13 100644 --- a/Tests/Loader/FileLoaderTest.php +++ b/Tests/Loader/FileLoaderTest.php @@ -262,7 +262,7 @@ public function testExcludeTrailingSlashConsistency(string $exclude) $this->assertFalse($container->has(DeeperBaz::class)); } - public function excludeTrailingSlashConsistencyProvider(): iterable + public static function excludeTrailingSlashConsistencyProvider(): iterable { yield ['Prototype/OtherDir/AnotherSub/']; yield ['Prototype/OtherDir/AnotherSub']; diff --git a/Tests/Loader/IniFileLoaderTest.php b/Tests/Loader/IniFileLoaderTest.php index 07f23f313..e81f7fbb2 100644 --- a/Tests/Loader/IniFileLoaderTest.php +++ b/Tests/Loader/IniFileLoaderTest.php @@ -58,7 +58,7 @@ public function testTypeConversionsWithNativePhp($key, $value, $supported) $this->assertSame($value, $expected['parameters'][$key], '->load() converts values to PHP types'); } - public function getTypeConversions() + public static function getTypeConversions() { return [ ['true_comment', true, true], diff --git a/Tests/Loader/LoaderResolverTest.php b/Tests/Loader/LoaderResolverTest.php index cfd8aa3cf..5980a3c63 100644 --- a/Tests/Loader/LoaderResolverTest.php +++ b/Tests/Loader/LoaderResolverTest.php @@ -42,7 +42,7 @@ protected function setUp(): void ]); } - public function provideResourcesToLoad() + public static function provideResourcesToLoad() { return [ ['ini_with_wrong_ext.xml', 'ini', IniFileLoader::class], diff --git a/Tests/Loader/PhpFileLoaderTest.php b/Tests/Loader/PhpFileLoaderTest.php index c324e9c13..8b141d257 100644 --- a/Tests/Loader/PhpFileLoaderTest.php +++ b/Tests/Loader/PhpFileLoaderTest.php @@ -85,7 +85,7 @@ public function testConfig($file) $this->assertStringMatchesFormatFile($fixtures.'/config/'.$file.'.expected.yml', $dumper->dump()); } - public function provideConfig() + public static function provideConfig() { yield ['basic']; yield ['object']; diff --git a/Tests/Loader/YamlFileLoaderTest.php b/Tests/Loader/YamlFileLoaderTest.php index 67f389ddc..cffed2c4c 100644 --- a/Tests/Loader/YamlFileLoaderTest.php +++ b/Tests/Loader/YamlFileLoaderTest.php @@ -93,7 +93,7 @@ public function testLoadInvalidFile($file) $loader->load($file.'.yml'); } - public function provideInvalidFiles() + public static function provideInvalidFiles() { return [ ['bad_parameters'], diff --git a/Tests/ParameterBag/ParameterBagTest.php b/Tests/ParameterBag/ParameterBagTest.php index 8bdabb7bb..f8db1cd21 100644 --- a/Tests/ParameterBag/ParameterBagTest.php +++ b/Tests/ParameterBag/ParameterBagTest.php @@ -84,7 +84,7 @@ public function testGetThrowParameterNotFoundException($parameterKey, $exception $bag->get($parameterKey); } - public function provideGetThrowParameterNotFoundExceptionData() + public static function provideGetThrowParameterNotFoundExceptionData() { return [ ['foo1', 'You have requested a non-existent parameter "foo1". Did you mean this: "foo"?'], @@ -244,7 +244,7 @@ public function testResolveStringWithSpacesReturnsString($expected, $test, $desc } } - public function stringsWithSpacesProvider() + public static function stringsWithSpacesProvider() { return [ ['bar', '%foo%', 'Parameters must be wrapped by %.'], From 478af3692661e7b884975b08095599ab8816794d Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Tue, 14 Feb 2023 20:50:14 +0100 Subject: [PATCH 06/19] [DependencyInjection] Add doc for RUNTIME_EXCEPTION_ON_INVALID_REFERENCE behavior --- Container.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Container.php b/Container.php index 6f61eb869..838756d47 100644 --- a/Container.php +++ b/Container.php @@ -36,11 +36,12 @@ class_exists(ArgumentServiceLocator::class); * The container can have four possible behaviors when a service * does not exist (or is not initialized for the last case): * - * * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception (the default) + * * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception at compilation time (the default) * * NULL_ON_INVALID_REFERENCE: Returns null * * IGNORE_ON_INVALID_REFERENCE: Ignores the wrapping command asking for the reference * (for instance, ignore a setter if the service does not exist) * * IGNORE_ON_UNINITIALIZED_REFERENCE: Ignores/returns null for uninitialized services or invalid references + * * RUNTIME_EXCEPTION_ON_INVALID_REFERENCE: Throws an exception at runtime * * @author Fabien Potencier * @author Johannes M. Schmitt From 5bc403d96622cf0091abd92c939eadecd4d07f94 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 16 Feb 2023 10:33:00 +0100 Subject: [PATCH 07/19] CS fix --- Compiler/AbstractRecursivePass.php | 4 ++-- Loader/YamlFileLoader.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Compiler/AbstractRecursivePass.php b/Compiler/AbstractRecursivePass.php index 362c5f571..f7a2176eb 100644 --- a/Compiler/AbstractRecursivePass.php +++ b/Compiler/AbstractRecursivePass.php @@ -175,9 +175,9 @@ protected function getConstructor(Definition $definition, bool $required) } /** - * @throws RuntimeException - * * @return \ReflectionFunctionAbstract + * + * @throws RuntimeException */ protected function getReflectionMethod(Definition $definition, string $method) { diff --git a/Loader/YamlFileLoader.php b/Loader/YamlFileLoader.php index 8756e89ed..2d9137cb5 100644 --- a/Loader/YamlFileLoader.php +++ b/Loader/YamlFileLoader.php @@ -707,9 +707,9 @@ private function parseDefinition(string $id, $service, string $file, array $defa * * @param string|array $callable A callable reference * - * @throws InvalidArgumentException When errors occur - * * @return string|array|Reference + * + * @throws InvalidArgumentException When errors occur */ private function parseCallable($callable, string $parameter, string $id, string $file) { From 2430da80ece229ea39b229e1d1c270940298f4b8 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Tue, 14 Feb 2023 21:51:46 +0100 Subject: [PATCH 08/19] [DependencyInjection] Fix autowire attribute with nullable parameters --- Compiler/AutowirePass.php | 10 ++-------- Tests/Compiler/AutowirePassTest.php | 14 ++++++++------ Tests/Fixtures/includes/autowiring_classes_80.php | 2 ++ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Compiler/AutowirePass.php b/Compiler/AutowirePass.php index f593ecb18..96e1169e8 100644 --- a/Compiler/AutowirePass.php +++ b/Compiler/AutowirePass.php @@ -276,23 +276,17 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a continue; } - $type = ProxyHelper::exportType($parameter, true); - if ($checkAttributes) { foreach ($parameter->getAttributes() as $attribute) { if (\in_array($attribute->getName(), [TaggedIterator::class, TaggedLocator::class, Autowire::class, MapDecorated::class], true)) { $arguments[$index] = $this->processAttribute($attribute->newInstance(), $parameter->allowsNull()); - break; + continue 2; } } - - if ('' !== ($arguments[$index] ?? '')) { - continue; - } } - if (!$type) { + if (!$type = ProxyHelper::exportType($parameter, true)) { if (isset($arguments[$index])) { continue; } diff --git a/Tests/Compiler/AutowirePassTest.php b/Tests/Compiler/AutowirePassTest.php index 171169ae7..fbe6adb25 100644 --- a/Tests/Compiler/AutowirePassTest.php +++ b/Tests/Compiler/AutowirePassTest.php @@ -1185,21 +1185,23 @@ public function testAutowireAttribute() $container->register('some.id', \stdClass::class); $container->setParameter('some.parameter', 'foo'); + $container->setParameter('null.parameter', null); (new ResolveClassPass())->process($container); (new AutowirePass())->process($container); $definition = $container->getDefinition(AutowireAttribute::class); - $this->assertCount(8, $definition->getArguments()); + $this->assertCount(9, $definition->getArguments()); $this->assertEquals(new Reference('some.id'), $definition->getArgument(0)); $this->assertEquals(new Expression("parameter('some.parameter')"), $definition->getArgument(1)); $this->assertSame('foo/bar', $definition->getArgument(2)); - $this->assertEquals(new Reference('some.id'), $definition->getArgument(3)); - $this->assertEquals(new Expression("parameter('some.parameter')"), $definition->getArgument(4)); - $this->assertSame('bar', $definition->getArgument(5)); - $this->assertSame('@bar', $definition->getArgument(6)); - $this->assertEquals(new Reference('invalid.id', ContainerInterface::NULL_ON_INVALID_REFERENCE), $definition->getArgument(7)); + $this->assertNull($definition->getArgument(3)); + $this->assertEquals(new Reference('some.id'), $definition->getArgument(4)); + $this->assertEquals(new Expression("parameter('some.parameter')"), $definition->getArgument(5)); + $this->assertSame('bar', $definition->getArgument(6)); + $this->assertSame('@bar', $definition->getArgument(7)); + $this->assertEquals(new Reference('invalid.id', ContainerInterface::NULL_ON_INVALID_REFERENCE), $definition->getArgument(8)); $container->compile(); diff --git a/Tests/Fixtures/includes/autowiring_classes_80.php b/Tests/Fixtures/includes/autowiring_classes_80.php index c1c772b68..30a575ff3 100644 --- a/Tests/Fixtures/includes/autowiring_classes_80.php +++ b/Tests/Fixtures/includes/autowiring_classes_80.php @@ -40,6 +40,8 @@ public function __construct( public string $expression, #[Autowire(value: '%some.parameter%/bar')] public string $value, + #[Autowire(value: '%null.parameter%')] + public ?string $nullableValue, #[Autowire('@some.id')] public \stdClass $serviceAsValue, #[Autowire("@=parameter('some.parameter')")] From 2ab1b773738a8eade059e8374ad25c5d16122a1c Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Thu, 2 Mar 2023 08:36:01 +0100 Subject: [PATCH 09/19] [DependencyInjection] Fix dumping array of enums parameters --- Dumper/PhpDumper.php | 25 ++++++++++++++----------- Tests/Dumper/PhpDumperTest.php | 10 ++++++++++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Dumper/PhpDumper.php b/Dumper/PhpDumper.php index dd548f3c1..66bf26879 100644 --- a/Dumper/PhpDumper.php +++ b/Dumper/PhpDumper.php @@ -92,6 +92,7 @@ class PhpDumper extends Dumper private $locatedIds = []; private $serviceLocatorTag; private $exportedVariables = []; + private $dynamicParameters = []; private $baseClass; /** @@ -141,6 +142,7 @@ public function dump(array $options = []) $this->targetDirRegex = null; $this->inlinedRequires = []; $this->exportedVariables = []; + $this->dynamicParameters = []; $options = array_merge([ 'class' => 'ProjectServiceContainer', 'base_class' => 'Container', @@ -223,11 +225,12 @@ public function dump(array $options = []) $this->preload = array_combine($options['preload_classes'], $options['preload_classes']); } + $code = $this->addDefaultParametersMethod(); $code = $this->startClass($options['class'], $baseClass, $this->inlineFactories && $proxyClasses). $this->addServices($services). $this->addDeprecatedAliases(). - $this->addDefaultParametersMethod() + $code ; $proxyClasses = $proxyClasses ?? $this->generateProxyClasses(); @@ -391,6 +394,7 @@ class %s extends {$options['class']} $this->circularReferences = []; $this->locatedIds = []; $this->exportedVariables = []; + $this->dynamicParameters = []; $this->preload = []; $unusedEnvs = []; @@ -1512,6 +1516,7 @@ private function addDefaultParametersMethod(): string if ($hasEnum || preg_match("/\\\$this->(?:getEnv\('(?:[-.\w]*+:)*+\w++'\)|targetDir\.'')/", $export[1])) { $dynamicPhp[$key] = sprintf('%scase %s: $value = %s; break;', $export[0], $this->export($key), $export[1]); + $this->dynamicParameters[$key] = true; } else { $php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]); } @@ -1916,20 +1921,18 @@ private function dumpLiteralClass(string $class): string private function dumpParameter(string $name): string { - if ($this->container->hasParameter($name)) { - $value = $this->container->getParameter($name); - $dumpedValue = $this->dumpValue($value, false); + if (!$this->container->hasParameter($name) || ($this->dynamicParameters[$name] ?? false)) { + return sprintf('$this->getParameter(%s)', $this->doExport($name)); + } - if (!$value || !\is_array($value)) { - return $dumpedValue; - } + $value = $this->container->getParameter($name); + $dumpedValue = $this->dumpValue($value, false); - if (!preg_match("/\\\$this->(?:getEnv\('(?:[-.\w]*+:)*+\w++'\)|targetDir\.'')/", $dumpedValue)) { - return sprintf('$this->parameters[%s]', $this->doExport($name)); - } + if (!$value || !\is_array($value)) { + return $dumpedValue; } - return sprintf('$this->getParameter(%s)', $this->doExport($name)); + return sprintf('$this->parameters[%s]', $this->doExport($name)); } private function getServiceCall(string $id, Reference $reference = null): string diff --git a/Tests/Dumper/PhpDumperTest.php b/Tests/Dumper/PhpDumperTest.php index 9763d2c15..694413d67 100644 --- a/Tests/Dumper/PhpDumperTest.php +++ b/Tests/Dumper/PhpDumperTest.php @@ -1237,6 +1237,11 @@ public function testDumpHandlesEnumeration() ->register('foo', FooClassWithEnumAttribute::class) ->setPublic(true) ->addArgument(FooUnitEnum::BAR); + $container + ->register('bar', \stdClass::class) + ->setPublic(true) + ->addArgument('%unit_enum%') + ->addArgument('%enum_array%'); $container->setParameter('unit_enum', FooUnitEnum::BAR); $container->setParameter('enum_array', [FooUnitEnum::BAR, FooUnitEnum::FOO]); @@ -1254,6 +1259,11 @@ public function testDumpHandlesEnumeration() $this->assertSame(FooUnitEnum::BAR, $container->getParameter('unit_enum')); $this->assertSame([FooUnitEnum::BAR, FooUnitEnum::FOO], $container->getParameter('enum_array')); $this->assertStringMatchesFormat(<<<'PHP' +%A + protected function getBarService() + { + return $this->services['bar'] = new \stdClass(\Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAR, $this->getParameter('enum_array')); + } %A private function getDynamicParameter(string $name) { From bb39fdd6eeba560a1cb020e171ceed4e6bc03860 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 6 Mar 2023 21:48:01 +0100 Subject: [PATCH 10/19] [Tests] Replace `setMethods()` by `onlyMethods()` and `addMethods()` --- Tests/Compiler/MergeExtensionConfigurationPassTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Compiler/MergeExtensionConfigurationPassTest.php b/Tests/Compiler/MergeExtensionConfigurationPassTest.php index f695c4287..2a719c15e 100644 --- a/Tests/Compiler/MergeExtensionConfigurationPassTest.php +++ b/Tests/Compiler/MergeExtensionConfigurationPassTest.php @@ -61,7 +61,7 @@ public function testExpressionLanguageProviderForwarding() public function testExtensionLoadGetAMergeExtensionConfigurationContainerBuilderInstance() { - $extension = $this->getMockBuilder(FooExtension::class)->setMethods(['load'])->getMock(); + $extension = $this->getMockBuilder(FooExtension::class)->onlyMethods(['load'])->getMock(); $extension->expects($this->once()) ->method('load') ->with($this->isType('array'), $this->isInstanceOf(MergeExtensionConfigurationContainerBuilder::class)) @@ -77,7 +77,7 @@ public function testExtensionLoadGetAMergeExtensionConfigurationContainerBuilder public function testExtensionConfigurationIsTrackedByDefault() { - $extension = $this->getMockBuilder(FooExtension::class)->setMethods(['getConfiguration'])->getMock(); + $extension = $this->getMockBuilder(FooExtension::class)->onlyMethods(['getConfiguration'])->getMock(); $extension->expects($this->exactly(2)) ->method('getConfiguration') ->willReturn(new FooConfiguration()); From 8b4a6d56f90f4a057f038e1452e8ef1ac87946ed Mon Sep 17 00:00:00 2001 From: Uladzimir Tsykun Date: Fri, 10 Mar 2023 02:15:31 +0100 Subject: [PATCH 11/19] Fix support binary values in parameters. --- Dumper/XmlDumper.php | 2 +- Tests/Fixtures/containers/container8.php | 2 ++ Tests/Fixtures/php/services8.php | 2 ++ Tests/Fixtures/xml/services8.xml | 2 ++ Tests/Fixtures/yaml/services8.yml | 2 ++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Dumper/XmlDumper.php b/Dumper/XmlDumper.php index 4f7b16d5f..402828b8e 100644 --- a/Dumper/XmlDumper.php +++ b/Dumper/XmlDumper.php @@ -320,7 +320,7 @@ private function convertParameters(array $parameters, string $type, \DOMElement $element->setAttribute('type', 'expression'); $text = $this->document->createTextNode(self::phpToXml((string) $value)); $element->appendChild($text); - } elseif (\is_string($value) && !preg_match('/^[^\x00-\x08\x0B\x0E-\x1A\x1C-\x1F\x7F]*+$/u', $value)) { + } elseif (\is_string($value) && !preg_match('/^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*+$/u', $value)) { $element->setAttribute('type', 'binary'); $text = $this->document->createTextNode(self::phpToXml(base64_encode($value))); $element->appendChild($text); diff --git a/Tests/Fixtures/containers/container8.php b/Tests/Fixtures/containers/container8.php index aa09db4c0..0caa0fe3e 100644 --- a/Tests/Fixtures/containers/container8.php +++ b/Tests/Fixtures/containers/container8.php @@ -9,8 +9,10 @@ 'bar' => 'foo is %%foo bar', 'escape' => '@escapeme', 'values' => [true, false, null, 0, 1000.3, 'true', 'false', 'null'], + 'utf-8 valid string' => "\u{021b}\u{1b56}\ttest", 'binary' => "\xf0\xf0\xf0\xf0", 'binary-control-char' => "This is a Bell char \x07", + 'console banner' => "\e[37;44m#StandWith\e[30;43mUkraine\e[0m", 'null string' => 'null', 'string of digits' => '123', 'string of digits prefixed with minus character' => '-123', diff --git a/Tests/Fixtures/php/services8.php b/Tests/Fixtures/php/services8.php index 827e4bf39..840bab52a 100644 --- a/Tests/Fixtures/php/services8.php +++ b/Tests/Fixtures/php/services8.php @@ -106,8 +106,10 @@ protected function getDefaultParameters(): array 6 => 'false', 7 => 'null', ], + 'utf-8 valid string' => 'ț᭖ test', 'binary' => 'ðððð', 'binary-control-char' => 'This is a Bell char ', + 'console banner' => '#StandWithUkraine', 'null string' => 'null', 'string of digits' => '123', 'string of digits prefixed with minus character' => '-123', diff --git a/Tests/Fixtures/xml/services8.xml b/Tests/Fixtures/xml/services8.xml index 906958d62..8e095e711 100644 --- a/Tests/Fixtures/xml/services8.xml +++ b/Tests/Fixtures/xml/services8.xml @@ -18,8 +18,10 @@ false null + ț᭖ test 8PDw8A== VGhpcyBpcyBhIEJlbGwgY2hhciAH + G1szNzs0NG0jU3RhbmRXaXRoG1szMDs0M21Va3JhaW5lG1swbQ== null 123 -123 diff --git a/Tests/Fixtures/yaml/services8.yml b/Tests/Fixtures/yaml/services8.yml index c410214e5..ef9782f0a 100644 --- a/Tests/Fixtures/yaml/services8.yml +++ b/Tests/Fixtures/yaml/services8.yml @@ -4,8 +4,10 @@ parameters: bar: 'foo is %%foo bar' escape: '@@escapeme' values: [true, false, null, 0, 1000.3, 'true', 'false', 'null'] + utf-8 valid string: "ț᭖\ttest" binary: !!binary 8PDw8A== binary-control-char: !!binary VGhpcyBpcyBhIEJlbGwgY2hhciAH + console banner: "\e[37;44m#StandWith\e[30;43mUkraine\e[0m" null string: 'null' string of digits: '123' string of digits prefixed with minus character: '-123' From a0464e770eb3d87803f2a922090ad52db8bdde91 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 6 Mar 2023 20:42:33 +0100 Subject: [PATCH 12/19] [Tests] Remove occurrences of `withConsecutive()` --- ...ContainerParametersResourceCheckerTest.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Tests/Config/ContainerParametersResourceCheckerTest.php b/Tests/Config/ContainerParametersResourceCheckerTest.php index 3563a3138..f13acc8f1 100644 --- a/Tests/Config/ContainerParametersResourceCheckerTest.php +++ b/Tests/Config/ContainerParametersResourceCheckerTest.php @@ -64,14 +64,17 @@ public static function isFreshProvider() yield 'fresh on every identical parameters' => [function (MockObject $container) { $container->expects(self::exactly(2))->method('hasParameter')->willReturn(true); $container->expects(self::exactly(2))->method('getParameter') - ->withConsecutive( - [self::equalTo('locales')], - [self::equalTo('default_locale')] - ) - ->willReturnMap([ - ['locales', ['fr', 'en']], - ['default_locale', 'fr'], - ]) + ->willReturnCallback(function (...$args) { + static $series = [ + [['locales'], ['fr', 'en']], + [['default_locale'], 'fr'], + ]; + + [$expectedArgs, $return] = array_shift($series); + self::assertSame($expectedArgs, $args); + + return $return; + }) ; }, true]; } From 143f83b12ab8a5385e2b6f04cb3ba2ecf1467486 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 30 Mar 2023 09:30:16 +0200 Subject: [PATCH 13/19] [DependencyInjection] Filter "container.excluded" services when using `findTaggedServiceIds()` --- ContainerBuilder.php | 2 +- Tests/ContainerBuilderTest.php | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ContainerBuilder.php b/ContainerBuilder.php index 4ac916b18..4fa6fca71 100644 --- a/ContainerBuilder.php +++ b/ContainerBuilder.php @@ -1212,7 +1212,7 @@ public function findTaggedServiceIds(string $name, bool $throwOnAbstract = false $this->usedTags[] = $name; $tags = []; foreach ($this->getDefinitions() as $id => $definition) { - if ($definition->hasTag($name)) { + if ($definition->hasTag($name) && !$definition->hasTag('container.excluded')) { if ($throwOnAbstract && $definition->isAbstract()) { throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must not be abstract.', $id, $name)); } diff --git a/Tests/ContainerBuilderTest.php b/Tests/ContainerBuilderTest.php index 042baf608..c87de498a 100644 --- a/Tests/ContainerBuilderTest.php +++ b/Tests/ContainerBuilderTest.php @@ -928,6 +928,11 @@ public function testfindTaggedServiceIds() ->addTag('bar', ['bar' => 'bar']) ->addTag('foo', ['foofoo' => 'foofoo']) ; + $builder + ->register('bar', 'Bar\FooClass') + ->addTag('foo') + ->addTag('container.excluded') + ; $this->assertEquals([ 'foo' => [ ['foo' => 'foo'], From b6195feacceb88fc58a02b69522b569e4c6188ac Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 30 Mar 2023 15:35:57 +0200 Subject: [PATCH 14/19] [DependencyInjection] Fix setting the class of auto-discovery services --- Loader/FileLoader.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Loader/FileLoader.php b/Loader/FileLoader.php index 6cb1e6ffd..f8a33182e 100644 --- a/Loader/FileLoader.php +++ b/Loader/FileLoader.php @@ -138,6 +138,7 @@ public function registerClasses(Definition $prototype, string $namespace, string continue; } + $definition->setClass($class); foreach (class_implements($class, false) as $interface) { $this->singlyImplemented[$interface] = ($this->singlyImplemented[$interface] ?? $class) !== $class ? false : $class; } @@ -253,7 +254,7 @@ private function findClasses(string $namespace, string $pattern, array $excludeP foreach ($excludePaths as $path => $_) { $class = $namespace.ltrim(str_replace('/', '\\', substr($path, $prefixLen, str_ends_with($path, '.php') ? -4 : null)), '\\'); if (!$this->container->has($class)) { - $this->container->register($class) + $this->container->register($class, $class) ->setAbstract(true) ->addTag('container.excluded', $attributes); } From bea617d2f7cb9bf2d3747b78613e67b067f40330 Mon Sep 17 00:00:00 2001 From: radar3301 Date: Fri, 14 Apr 2023 14:19:33 -0600 Subject: [PATCH 15/19] [DependencyInjection] Fallback to default value when autowiring undefined parameters for optional arguments --- Compiler/AutowirePass.php | 14 ++++++-- Tests/Compiler/AutowirePassTest.php | 32 +++++++++++++++++++ .../includes/autowiring_classes_80.php | 11 +++++++ 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/Compiler/AutowirePass.php b/Compiler/AutowirePass.php index 96e1169e8..0a61f46e5 100644 --- a/Compiler/AutowirePass.php +++ b/Compiler/AutowirePass.php @@ -23,6 +23,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\TypedReference; @@ -279,9 +280,16 @@ private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, a if ($checkAttributes) { foreach ($parameter->getAttributes() as $attribute) { if (\in_array($attribute->getName(), [TaggedIterator::class, TaggedLocator::class, Autowire::class, MapDecorated::class], true)) { - $arguments[$index] = $this->processAttribute($attribute->newInstance(), $parameter->allowsNull()); - - continue 2; + try { + $arguments[$index] = $this->processAttribute($attribute->newInstance(), $parameter->allowsNull()); + continue 2; + } catch (ParameterNotFoundException $e) { + if (!$parameter->isDefaultValueAvailable()) { + throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e); + } + $arguments[$index] = clone $this->defaultArgument; + $arguments[$index]->value = $parameter->getDefaultValue(); + } } } } diff --git a/Tests/Compiler/AutowirePassTest.php b/Tests/Compiler/AutowirePassTest.php index fbe6adb25..5209d6fd1 100644 --- a/Tests/Compiler/AutowirePassTest.php +++ b/Tests/Compiler/AutowirePassTest.php @@ -1217,6 +1217,38 @@ public function testAutowireAttribute() $this->assertNull($service->invalid); } + public function testAutowireAttributeNullFallbackTestRequired() + { + $container = new ContainerBuilder(); + + $container->register('foo', AutowireAttributeNullFallback::class) + ->setAutowired(true) + ->setPublic(true) + ; + + $this->expectException(AutowiringFailedException::class); + $this->expectExceptionMessage('You have requested a non-existent parameter "required.parameter".'); + (new AutowirePass())->process($container); + } + + public function testAutowireAttributeNullFallbackTestOptional() + { + $container = new ContainerBuilder(); + + $container->register('foo', AutowireAttributeNullFallback::class) + ->setAutowired(true) + ->setPublic(true) + ; + + $container->setParameter('required.parameter', 'foo'); + + (new AutowirePass())->process($container); + + $definition = $container->getDefinition('foo'); + + $this->assertSame(['foo'], $definition->getArguments()); + } + public function testAsDecoratorAttribute() { $container = new ContainerBuilder(); diff --git a/Tests/Fixtures/includes/autowiring_classes_80.php b/Tests/Fixtures/includes/autowiring_classes_80.php index 30a575ff3..98ece976a 100644 --- a/Tests/Fixtures/includes/autowiring_classes_80.php +++ b/Tests/Fixtures/includes/autowiring_classes_80.php @@ -56,6 +56,17 @@ public function __construct( } } +class AutowireAttributeNullFallback +{ + public function __construct( + #[Autowire('%required.parameter%')] + public string $required, + #[Autowire('%optional.parameter%')] + public ?string $optional = null, + ) { + } +} + interface AsDecoratorInterface { } From 3bcb9592b9bd1fb7634e8c5ee68323dbd074e1b5 Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Wed, 19 Apr 2023 20:17:54 +0800 Subject: [PATCH 16/19] [DependencyInjection] Fix support for inner collections when using `` --- Loader/schema/dic/services/services-1.0.xsd | 20 +++++- .../xml/bindings_and_inner_collections.xml | 69 +++++++++++++++++++ Tests/Loader/XmlFileLoaderTest.php | 28 ++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 Tests/Fixtures/xml/bindings_and_inner_collections.xml diff --git a/Loader/schema/dic/services/services-1.0.xsd b/Loader/schema/dic/services/services-1.0.xsd index 20e978667..199ffe9a5 100644 --- a/Loader/schema/dic/services/services-1.0.xsd +++ b/Loader/schema/dic/services/services-1.0.xsd @@ -275,7 +275,7 @@ - + @@ -286,6 +286,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Tests/Fixtures/xml/bindings_and_inner_collections.xml b/Tests/Fixtures/xml/bindings_and_inner_collections.xml new file mode 100644 index 000000000..0e4d6bc0c --- /dev/null +++ b/Tests/Fixtures/xml/bindings_and_inner_collections.xml @@ -0,0 +1,69 @@ + + + + + + item.1 + item.2 + + + + + item.1 + item.2 + + + + + item.1 + item.2 + item.3 + item.4 + + + + + item.1 + item.2 + item.3 + item.4 + + + + + item.1 + item.2 + + item.3.1 + item.3.2 + + + + + + item.1 + + item.2.1 + item.2.2 + + item.3 + + + + + item.1 + item.2 + + + + + item.1 + item.2 + + item.3.1 + item.3.2 + + + + + diff --git a/Tests/Loader/XmlFileLoaderTest.php b/Tests/Loader/XmlFileLoaderTest.php index 7914d1b29..c0786c466 100644 --- a/Tests/Loader/XmlFileLoaderTest.php +++ b/Tests/Loader/XmlFileLoaderTest.php @@ -1166,4 +1166,32 @@ public function testClosure() $definition = $container->getDefinition('closure_property')->getProperties()['foo']; $this->assertEquals((new Definition('Closure'))->setFactory(['Closure', 'fromCallable'])->addArgument(new Reference('bar')), $definition); } + + /** + * @dataProvider dataForBindingsAndInnerCollections + */ + public function testBindingsAndInnerCollections($bindName, $expected) + { + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + $loader->load('bindings_and_inner_collections.xml'); + (new ResolveBindingsPass())->process($container); + $definition = $container->getDefinition($bindName); + $actual = $definition->getBindings()['$foo']->getValues()[0]; + $this->assertEquals($actual, $expected); + } + + public function dataForBindingsAndInnerCollections() + { + return [ + ['bar1', ['item.1', 'item.2']], + ['bar2', ['item.1', 'item.2']], + ['bar3', ['item.1', 'item.2', 'item.3', 'item.4']], + ['bar4', ['item.1', 'item.3', 'item.4']], + ['bar5', ['item.1', 'item.2', ['item.3.1', 'item.3.2']]], + ['bar6', ['item.1', ['item.2.1', 'item.2.2'], 'item.3']], + ['bar7', new IteratorArgument(['item.1', 'item.2'])], + ['bar8', new IteratorArgument(['item.1', 'item.2', ['item.3.1', 'item.3.2']])], + ]; + } } From c36180b3d4904efddee149a6019a03fb3a6447e9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 20 Apr 2023 15:14:34 +0200 Subject: [PATCH 17/19] Fix test provider --- Tests/Loader/XmlFileLoaderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Loader/XmlFileLoaderTest.php b/Tests/Loader/XmlFileLoaderTest.php index c0786c466..5defa4358 100644 --- a/Tests/Loader/XmlFileLoaderTest.php +++ b/Tests/Loader/XmlFileLoaderTest.php @@ -1181,7 +1181,7 @@ public function testBindingsAndInnerCollections($bindName, $expected) $this->assertEquals($actual, $expected); } - public function dataForBindingsAndInnerCollections() + public static function dataForBindingsAndInnerCollections() { return [ ['bar1', ['item.1', 'item.2']], From bb7b7988c898c94f5338e16403c52b5a3cae1d93 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 21 Apr 2023 16:50:40 +0200 Subject: [PATCH 18/19] [Contracts] Rename ServiceLocatorTest --- Tests/ServiceLocatorTest.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Tests/ServiceLocatorTest.php b/Tests/ServiceLocatorTest.php index d8a3a0006..45f20bf10 100644 --- a/Tests/ServiceLocatorTest.php +++ b/Tests/ServiceLocatorTest.php @@ -18,9 +18,14 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ServiceLocator; use Symfony\Contracts\Service\ServiceSubscriberInterface; -use Symfony\Contracts\Service\Test\ServiceLocatorTest as BaseServiceLocatorTest; +use Symfony\Contracts\Service\Test\ServiceLocatorTest as LegacyServiceLocatorTestCase; +use Symfony\Contracts\Service\Test\ServiceLocatorTestCase; -class ServiceLocatorTest extends BaseServiceLocatorTest +if (!class_exists(ServiceLocatorTestCase::class)) { + class_alias(LegacyServiceLocatorTestCase::class, ServiceLocatorTestCase::class); +} + +class ServiceLocatorTest extends ServiceLocatorTestCase { public function getServiceLocator(array $factories): ContainerInterface { From 8b4789e38b61a2b12dae778d69734434ced4f8c4 Mon Sep 17 00:00:00 2001 From: MatTheCat Date: Fri, 21 Apr 2023 12:16:37 +0200 Subject: [PATCH 19/19] [DependencyInjection] Do not ignore tags `name` attribute when it does not define their name --- Loader/XmlFileLoader.php | 5 +++-- Tests/Fixtures/xml/tag_with_name_attribute.xml | 11 +++++++++++ Tests/Loader/XmlFileLoaderTest.php | 10 ++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 Tests/Fixtures/xml/tag_with_name_attribute.xml diff --git a/Loader/XmlFileLoader.php b/Loader/XmlFileLoader.php index ab76eb9e0..b350900ea 100644 --- a/Loader/XmlFileLoader.php +++ b/Loader/XmlFileLoader.php @@ -336,13 +336,14 @@ private function parseDefinition(\DOMElement $service, string $file, Definition $tags = $this->getChildren($service, 'tag'); foreach ($tags as $tag) { - if ('' === $tagName = $tag->childElementCount || '' === $tag->nodeValue ? $tag->getAttribute('name') : $tag->nodeValue) { + $tagNameComesFromAttribute = $tag->childElementCount || '' === $tag->nodeValue; + if ('' === $tagName = $tagNameComesFromAttribute ? $tag->getAttribute('name') : $tag->nodeValue) { throw new InvalidArgumentException(sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', (string) $service->getAttribute('id'), $file)); } $parameters = $this->getTagAttributes($tag, sprintf('The attribute name of tag "%s" for service "%s" in %s must be a non-empty string.', $tagName, (string) $service->getAttribute('id'), $file)); foreach ($tag->attributes as $name => $node) { - if ('name' === $name) { + if ($tagNameComesFromAttribute && 'name' === $name) { continue; } diff --git a/Tests/Fixtures/xml/tag_with_name_attribute.xml b/Tests/Fixtures/xml/tag_with_name_attribute.xml new file mode 100644 index 000000000..db67200e5 --- /dev/null +++ b/Tests/Fixtures/xml/tag_with_name_attribute.xml @@ -0,0 +1,11 @@ + + + + + + tag_name + + + diff --git a/Tests/Loader/XmlFileLoaderTest.php b/Tests/Loader/XmlFileLoaderTest.php index 5defa4358..9c6a0e5f6 100644 --- a/Tests/Loader/XmlFileLoaderTest.php +++ b/Tests/Loader/XmlFileLoaderTest.php @@ -1194,4 +1194,14 @@ public static function dataForBindingsAndInnerCollections() ['bar8', new IteratorArgument(['item.1', 'item.2', ['item.3.1', 'item.3.2']])], ]; } + + public function testTagNameAttribute() + { + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + $loader->load('tag_with_name_attribute.xml'); + + $definition = $container->getDefinition('foo'); + $this->assertSame([['name' => 'name_attribute']], $definition->getTag('tag_name')); + } }