diff --git a/.github/patch-types.php b/.github/patch-types.php index 311e9e7ee4..d3dfc9073d 100644 --- a/.github/patch-types.php +++ b/.github/patch-types.php @@ -30,6 +30,7 @@ foreach ($loader->getClassMap() as $class => $file) { case false !== strpos($file, '/src/Symfony/Component/Config/Tests/Fixtures/ParseError.php'): case false !== strpos($file, '/src/Symfony/Component/Debug/Tests/Fixtures/'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Compiler/OptionalServiceClass.php'): + case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/UnionConstructor.php'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/uniontype_classes.php'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ParentNotExists.php'): diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php index ae1a2ec204..752633ce17 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php @@ -153,26 +153,27 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass /** * @throws InvalidParameterTypeException When a parameter is not compatible with the declared type */ - private function checkType(Definition $checkedDefinition, $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix, string $type = null): void + private function checkType(Definition $checkedDefinition, $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix, \ReflectionType $reflectionType = null): void { - if (null === $type) { - $type = $parameter->getType(); + $reflectionType = $reflectionType ?? $parameter->getType(); - if ($type instanceof \ReflectionUnionType) { - foreach ($type->getTypes() as $type) { - try { - $this->checkType($checkedDefinition, $value, $parameter, $envPlaceholderUniquePrefix, $type); + if ($reflectionType instanceof \ReflectionUnionType) { + foreach ($reflectionType->getTypes() as $t) { + try { + $this->checkType($checkedDefinition, $value, $parameter, $envPlaceholderUniquePrefix, $t); - return; - } catch (InvalidParameterTypeException $e) { - } + return; + } catch (InvalidParameterTypeException $e) { } - - throw new InvalidParameterTypeException($this->currentId, $e->getCode(), $parameter); } - $type = $type->getName(); + throw new InvalidParameterTypeException($this->currentId, $e->getCode(), $parameter); } + if (!$reflectionType instanceof \ReflectionNamedType) { + return; + } + + $type = $reflectionType->getName(); if ($value instanceof Reference) { if (!$this->container->has($value = (string) $value)) { @@ -285,7 +286,7 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass $checkFunction = sprintf('is_%s', $type); - if (!$parameter->getType()->isBuiltin() || !$checkFunction($value)) { + if (!$reflectionType->isBuiltin() || !$checkFunction($value)) { throw new InvalidParameterTypeException($this->currentId, \is_object($value) ? $class : \gettype($value), $parameter); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php index 0842f26879..c683c3ed0a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php @@ -26,6 +26,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPa use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\BarOptionalArgumentNotNull; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Foo; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\FooObject; +use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\UnionConstructor; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Waldo; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Wobble; use Symfony\Component\ExpressionLanguage\Expression; @@ -803,4 +804,72 @@ class CheckTypeDeclarationsPassTest extends TestCase putenv('ARRAY='); } + + /** + * @requires PHP 8 + */ + public function testUnionTypePassesWithReference() + { + $container = new ContainerBuilder(); + + $container->register('foo', Foo::class); + $container->register('union', UnionConstructor::class) + ->setArguments([new Reference('foo')]); + + (new CheckTypeDeclarationsPass(true))->process($container); + + $this->addToAssertionCount(1); + } + + /** + * @requires PHP 8 + */ + public function testUnionTypePassesWithBuiltin() + { + $container = new ContainerBuilder(); + + $container->register('union', UnionConstructor::class) + ->setArguments([42]); + + (new CheckTypeDeclarationsPass(true))->process($container); + + $this->addToAssertionCount(1); + } + + /** + * @requires PHP 8 + */ + public function testUnionTypeFailsWithReference() + { + $container = new ContainerBuilder(); + + $container->register('waldo', Waldo::class); + $container->register('union', UnionConstructor::class) + ->setArguments([new Reference('waldo')]); + + $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid definition for service "union": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\UnionConstructor::__construct" accepts "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Foo|int", "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Waldo" passed.'); + + (new CheckTypeDeclarationsPass(true))->process($container); + + $this->addToAssertionCount(1); + } + + /** + * @requires PHP 8 + */ + public function testUnionTypeFailsWithBuiltin() + { + $container = new ContainerBuilder(); + + $container->register('union', UnionConstructor::class) + ->setArguments([[1, 2, 3]]); + + $this->expectException(\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid definition for service "union": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\UnionConstructor::__construct" accepts "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Foo|int", "array" passed.'); + + (new CheckTypeDeclarationsPass(true))->process($container); + + $this->addToAssertionCount(1); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/UnionConstructor.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/UnionConstructor.php new file mode 100644 index 0000000000..5ba5972863 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/UnionConstructor.php @@ -0,0 +1,10 @@ +