From da332ce7e38ab5e92e0011284026a923a82fd14f Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 31 Jan 2024 11:51:21 +0100 Subject: [PATCH 1/4] fix tests --- Tests/Dumper/YamlDumperTest.php | 6 +++++- .../yaml/services_with_enumeration_enum_tag.yml | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 Tests/Fixtures/yaml/services_with_enumeration_enum_tag.yml diff --git a/Tests/Dumper/YamlDumperTest.php b/Tests/Dumper/YamlDumperTest.php index 68931050b..f9ff3fff7 100644 --- a/Tests/Dumper/YamlDumperTest.php +++ b/Tests/Dumper/YamlDumperTest.php @@ -163,7 +163,11 @@ public function testDumpHandlesEnumeration() $container->compile(); $dumper = new YamlDumper($container); - $this->assertEquals(file_get_contents(self::$fixturesPath.'/yaml/services_with_enumeration.yml'), $dumper->dump()); + if (str_starts_with(Yaml::dump(FooUnitEnum::BAR), '!php/enum')) { + $this->assertEquals(file_get_contents(self::$fixturesPath.'/yaml/services_with_enumeration_enum_tag.yml'), $dumper->dump()); + } else { + $this->assertEquals(file_get_contents(self::$fixturesPath.'/yaml/services_with_enumeration.yml'), $dumper->dump()); + } } /** diff --git a/Tests/Fixtures/yaml/services_with_enumeration_enum_tag.yml b/Tests/Fixtures/yaml/services_with_enumeration_enum_tag.yml new file mode 100644 index 000000000..c34ce4f8e --- /dev/null +++ b/Tests/Fixtures/yaml/services_with_enumeration_enum_tag.yml @@ -0,0 +1,13 @@ +parameters: + unit_enum: !php/enum Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAR + enum_array: [!php/enum Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAR, !php/enum Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::FOO] + +services: + service_container: + class: Symfony\Component\DependencyInjection\ContainerInterface + public: true + synthetic: true + Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute: + class: Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute + public: true + arguments: [!php/const 'Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum::BAR'] From 774d507035582651add92ed4add4008ebadd98a0 Mon Sep 17 00:00:00 2001 From: kor3k Date: Thu, 8 Feb 2024 23:46:29 +0100 Subject: [PATCH 2/4] [DependencyInjection] fix unable to make lazy service from readonly class --- Dumper/PhpDumper.php | 4 +++- LazyProxy/PhpDumper/LazyServiceDumper.php | 2 +- Tests/Fixtures/ReadonlyTest.php | 20 +++++++++++++++++++ .../PhpDumper/LazyServiceDumperTest.php | 13 ++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 Tests/Fixtures/ReadonlyTest.php diff --git a/Dumper/PhpDumper.php b/Dumper/PhpDumper.php index 81b500c5a..23e65483b 100644 --- a/Dumper/PhpDumper.php +++ b/Dumper/PhpDumper.php @@ -624,7 +624,9 @@ private function generateProxyClasses(): array $proxyCode = substr(self::stripComments($proxyCode), 5); } - $proxyClass = explode(' ', $this->inlineRequires ? substr($proxyCode, \strlen($code)) : $proxyCode, 3)[1]; + $proxyClass = $this->inlineRequires ? substr($proxyCode, \strlen($code)) : $proxyCode; + $i = strpos($proxyClass, 'class'); + $proxyClass = substr($proxyClass, 6 + $i, strpos($proxyClass, ' ', 7 + $i) - $i - 6); if ($this->asFiles || $this->namespace) { $proxyCode .= "\nif (!\\class_exists('$proxyClass', false)) {\n \\class_alias(__NAMESPACE__.'\\\\$proxyClass', '$proxyClass', false);\n}\n"; diff --git a/LazyProxy/PhpDumper/LazyServiceDumper.php b/LazyProxy/PhpDumper/LazyServiceDumper.php index b1df5f0f0..0d4426c44 100644 --- a/LazyProxy/PhpDumper/LazyServiceDumper.php +++ b/LazyProxy/PhpDumper/LazyServiceDumper.php @@ -105,7 +105,7 @@ public function getProxyCode(Definition $definition, ?string $id = null): string if ($asGhostObject) { try { - return 'class '.$proxyClass.ProxyHelper::generateLazyGhost($class); + return (\PHP_VERSION_ID >= 80200 && $class?->isReadOnly() ? 'readonly ' : '').'class '.$proxyClass.ProxyHelper::generateLazyGhost($class); } catch (LogicException $e) { throw new InvalidArgumentException(sprintf('Cannot generate lazy ghost for service "%s".', $id ?? $definition->getClass()), 0, $e); } diff --git a/Tests/Fixtures/ReadonlyTest.php b/Tests/Fixtures/ReadonlyTest.php new file mode 100644 index 000000000..f49931bf3 --- /dev/null +++ b/Tests/Fixtures/ReadonlyTest.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +readonly class ReadonlyTest +{ + public function say(): string + { + return 'hello'; + } +} diff --git a/Tests/LazyProxy/PhpDumper/LazyServiceDumperTest.php b/Tests/LazyProxy/PhpDumper/LazyServiceDumperTest.php index 064bfc3cc..9a5da0721 100644 --- a/Tests/LazyProxy/PhpDumper/LazyServiceDumperTest.php +++ b/Tests/LazyProxy/PhpDumper/LazyServiceDumperTest.php @@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper; +use Symfony\Component\DependencyInjection\Tests\Fixtures\ReadonlyTest; class LazyServiceDumperTest extends TestCase { @@ -52,6 +53,18 @@ public function testInvalidClass() $this->expectExceptionMessage('Invalid "proxy" tag for service "stdClass": class "stdClass" doesn\'t implement "Psr\Container\ContainerInterface".'); $dumper->getProxyCode($definition); } + + /** + * @requires PHP 8.2 + */ + public function testReadonlyClass() + { + $dumper = new LazyServiceDumper(); + $definition = (new Definition(ReadonlyTest::class))->setLazy(true); + + $this->assertTrue($dumper->isProxyCandidate($definition)); + $this->assertStringContainsString('readonly class ReadonlyTestGhost', $dumper->getProxyCode($definition)); + } } final class TestContainer implements ContainerInterface From ab84c8922d1e1852c8880296ff231ddae6a35cf4 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 9 Feb 2024 09:32:06 +0100 Subject: [PATCH 3/4] fix tests --- Tests/Fixtures/{ReadonlyTest.php => ReadOnlyClass.php} | 2 +- Tests/LazyProxy/PhpDumper/LazyServiceDumperTest.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) rename Tests/Fixtures/{ReadonlyTest.php => ReadOnlyClass.php} (92%) diff --git a/Tests/Fixtures/ReadonlyTest.php b/Tests/Fixtures/ReadOnlyClass.php similarity index 92% rename from Tests/Fixtures/ReadonlyTest.php rename to Tests/Fixtures/ReadOnlyClass.php index f49931bf3..b247ea216 100644 --- a/Tests/Fixtures/ReadonlyTest.php +++ b/Tests/Fixtures/ReadOnlyClass.php @@ -11,7 +11,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Fixtures; -readonly class ReadonlyTest +readonly class ReadOnlyClass { public function say(): string { diff --git a/Tests/LazyProxy/PhpDumper/LazyServiceDumperTest.php b/Tests/LazyProxy/PhpDumper/LazyServiceDumperTest.php index 9a5da0721..467972a88 100644 --- a/Tests/LazyProxy/PhpDumper/LazyServiceDumperTest.php +++ b/Tests/LazyProxy/PhpDumper/LazyServiceDumperTest.php @@ -16,7 +16,7 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper; -use Symfony\Component\DependencyInjection\Tests\Fixtures\ReadonlyTest; +use Symfony\Component\DependencyInjection\Tests\Fixtures\ReadOnlyClass; class LazyServiceDumperTest extends TestCase { @@ -55,15 +55,15 @@ public function testInvalidClass() } /** - * @requires PHP 8.2 + * @requires PHP 8.3 */ public function testReadonlyClass() { $dumper = new LazyServiceDumper(); - $definition = (new Definition(ReadonlyTest::class))->setLazy(true); + $definition = (new Definition(ReadOnlyClass::class))->setLazy(true); $this->assertTrue($dumper->isProxyCandidate($definition)); - $this->assertStringContainsString('readonly class ReadonlyTestGhost', $dumper->getProxyCode($definition)); + $this->assertStringContainsString('readonly class ReadOnlyClassGhost', $dumper->getProxyCode($definition)); } } From cc1fb237cd0e6da33005062b13b8485deb6e4440 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 22 Feb 2024 11:42:45 -0500 Subject: [PATCH 4/4] [DependencyInjection] Fix computing error messages involving service locators --- ...xceptionOnInvalidReferenceBehaviorPass.php | 51 +++++++++++++------ ...tionOnInvalidReferenceBehaviorPassTest.php | 20 ++++++-- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php b/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php index fd3173831..f4d01d8bc 100644 --- a/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php +++ b/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php @@ -25,9 +25,6 @@ class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass { private $serviceLocatorContextIds = []; - /** - * {@inheritdoc} - */ public function process(ContainerBuilder $container) { $this->serviceLocatorContextIds = []; @@ -58,15 +55,7 @@ protected function processValue($value, bool $isRoot = false) if (isset($this->serviceLocatorContextIds[$currentId])) { $currentId = $this->serviceLocatorContextIds[$currentId]; $locator = $this->container->getDefinition($this->currentId)->getFactory()[0]; - - foreach ($locator->getArgument(0) as $k => $v) { - if ($v->getValues()[0] === $value) { - if ($k !== $id) { - $currentId = $k.'" in the container provided to "'.$currentId; - } - throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id)); - } - } + $this->throwServiceNotFoundException($value, $currentId, $locator->getArgument(0)); } if ('.' === $currentId[0] && $graph->hasNode($currentId)) { @@ -80,14 +69,21 @@ protected function processValue($value, bool $isRoot = false) $currentId = $sourceId; break; } + + if (isset($this->serviceLocatorContextIds[$sourceId])) { + $currentId = $this->serviceLocatorContextIds[$sourceId]; + $locator = $this->container->getDefinition($this->currentId); + $this->throwServiceNotFoundException($value, $currentId, $locator->getArgument(0)); + } } } - throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id)); + $this->throwServiceNotFoundException($value, $currentId, $value); } - private function getAlternatives(string $id): array + private function throwServiceNotFoundException(Reference $ref, string $sourceId, $value): void { + $id = (string) $ref; $alternatives = []; foreach ($this->container->getServiceIds() as $knownId) { if ('' === $knownId || '.' === $knownId[0]) { @@ -100,6 +96,31 @@ private function getAlternatives(string $id): array } } - return $alternatives; + $pass = new class() extends AbstractRecursivePass { + public $ref; + public $sourceId; + public $alternatives; + + /** + * @return mixed + */ + public function processValue($value, bool $isRoot = false) + { + if ($this->ref !== $value) { + return parent::processValue($value, $isRoot); + } + $sourceId = $this->sourceId; + if (null !== $this->currentId && $this->currentId !== (string) $value) { + $sourceId = $this->currentId.'" in the container provided to "'.$sourceId; + } + + throw new ServiceNotFoundException((string) $value, $sourceId, null, $this->alternatives); + } + }; + $pass->ref = $ref; + $pass->sourceId = $sourceId; + $pass->alternatives = $alternatives; + + $pass->processValue($value, true); } } diff --git a/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php b/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php index b2bd5023d..f98d06560 100644 --- a/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php +++ b/Tests/Compiler/CheckExceptionOnInvalidReferenceBehaviorPassTest.php @@ -82,7 +82,11 @@ public function testProcessDefinitionWithBindings() $this->addToAssertionCount(1); } - public function testWithErroredServiceLocator() + /** + * @testWith [true] + * [false] + */ + public function testWithErroredServiceLocator(bool $inline) { $this->expectException(ServiceNotFoundException::class); $this->expectExceptionMessage('The service "foo" in the container provided to "bar" has a dependency on a non-existent service "baz".'); @@ -91,11 +95,17 @@ public function testWithErroredServiceLocator() ServiceLocatorTagPass::register($container, ['foo' => new Reference('baz')], 'bar'); (new AnalyzeServiceReferencesPass())->process($container); - (new InlineServiceDefinitionsPass())->process($container); + if ($inline) { + (new InlineServiceDefinitionsPass())->process($container); + } $this->process($container); } - public function testWithErroredHiddenService() + /** + * @testWith [true] + * [false] + */ + public function testWithErroredHiddenService(bool $inline) { $this->expectException(ServiceNotFoundException::class); $this->expectExceptionMessage('The service "bar" has a dependency on a non-existent service "foo".'); @@ -104,7 +114,9 @@ public function testWithErroredHiddenService() ServiceLocatorTagPass::register($container, ['foo' => new Reference('foo')], 'bar'); (new AnalyzeServiceReferencesPass())->process($container); - (new InlineServiceDefinitionsPass())->process($container); + if ($inline) { + (new InlineServiceDefinitionsPass())->process($container); + } $this->process($container); }