diff --git a/.github/patch-types.php b/.github/patch-types.php index 95e56b5984..ed89136da8 100644 --- a/.github/patch-types.php +++ b/.github/patch-types.php @@ -31,7 +31,9 @@ foreach ($loader->getClassMap() as $class => $file) { 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/includes/autowiring_classes.php'): + case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/intersectiontype_classes.php'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/MultipleArgumentsOptionalScalarNotReallyOptional.php'): + case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/IntersectionConstructor.php'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ParentNotExists.php'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Preload/'): case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/BadClasses/MissingParent.php'): diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php index c6b17efd4a..6f4cfd151a 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckTypeDeclarationsPass.php @@ -174,6 +174,13 @@ final class CheckTypeDeclarationsPass extends AbstractRecursivePass throw new InvalidParameterTypeException($this->currentId, $e->getCode(), $parameter); } + if ($reflectionType instanceof \ReflectionIntersectionType) { + foreach ($reflectionType->getTypes() as $t) { + $this->checkType($checkedDefinition, $value, $parameter, $envPlaceholderUniquePrefix, $t); + } + + return; + } if (!$reflectionType instanceof \ReflectionNamedType) { return; } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/Preloader.php b/src/Symfony/Component/DependencyInjection/Dumper/Preloader.php index 125a555fda..158900bc7b 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/Preloader.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/Preloader.php @@ -98,7 +98,7 @@ class Preloader return; } - foreach ($t instanceof \ReflectionUnionType ? $t->getTypes() : [$t] as $t) { + foreach (($t instanceof \ReflectionUnionType || $t instanceof \ReflectionIntersectionType) ? $t->getTypes() : [$t] as $t) { if (!$t->isBuiltin()) { self::doPreload($t instanceof \ReflectionNamedType ? $t->getName() : $t, $preloaded); } diff --git a/src/Symfony/Component/DependencyInjection/LazyProxy/ProxyHelper.php b/src/Symfony/Component/DependencyInjection/LazyProxy/ProxyHelper.php index f4a5afbb89..32b94df04b 100644 --- a/src/Symfony/Component/DependencyInjection/LazyProxy/ProxyHelper.php +++ b/src/Symfony/Component/DependencyInjection/LazyProxy/ProxyHelper.php @@ -33,22 +33,31 @@ class ProxyHelper } $types = []; + $glue = '|'; + if ($type instanceof \ReflectionUnionType) { + $reflectionTypes = $type->getTypes(); + } elseif ($type instanceof \ReflectionIntersectionType) { + $reflectionTypes = $type->getTypes(); + $glue = '&'; + } elseif ($type instanceof \ReflectionNamedType) { + $reflectionTypes = [$type]; + } else { + return null; + } - foreach ($type instanceof \ReflectionUnionType ? $type->getTypes() : [$type] as $type) { - $name = $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type; - + foreach ($reflectionTypes as $type) { if ($type->isBuiltin()) { if (!$noBuiltin) { - $types[] = $name; + $types[] = $type->getName(); } continue; } - $lcName = strtolower($name); + $lcName = strtolower($type->getName()); $prefix = $noBuiltin ? '' : '\\'; if ('self' !== $lcName && 'parent' !== $lcName) { - $types[] = '' !== $prefix ? $prefix.$name : $name; + $types[] = $prefix.$type->getName(); continue; } if (!$r instanceof \ReflectionMethod) { @@ -61,6 +70,6 @@ class ProxyHelper } } - return $types ? implode('|', $types) : null; + return $types ? implode($glue, $types) : null; } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 3478e79d4b..4e90bfb1c7 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -255,6 +255,25 @@ class AutowirePassTest extends TestCase $pass->process($container); } + /** + * @requires PHP 8.1 + */ + public function testTypeNotGuessableIntersectionType() + { + $this->expectException(AutowiringFailedException::class); + $this->expectExceptionMessage('Cannot autowire service "a": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\IntersectionClasses::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface&Symfony\Component\DependencyInjection\Tests\Compiler\AnotherInterface" but this class was not found.'); + $container = new ContainerBuilder(); + + $container->register(CollisionInterface::class); + $container->register(AnotherInterface::class); + + $aDefinition = $container->register('a', IntersectionClasses::class); + $aDefinition->setAutowired(true); + + $pass = new AutowirePass(); + $pass->process($container); + } + public function testTypeNotGuessableWithTypeSet() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php index 6dc382a454..adb725cc99 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckTypeDeclarationsPassTest.php @@ -29,8 +29,10 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPa use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Deprecated; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Foo; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\FooObject; +use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\IntersectionConstructor; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\UnionConstructor; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Waldo; +use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\WaldoFoo; use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Wobble; use Symfony\Component\ExpressionLanguage\Expression; @@ -953,4 +955,37 @@ class CheckTypeDeclarationsPassTest extends TestCase $this->addToAssertionCount(1); } + + /** + * @requires PHP 8.1 + */ + public function testIntersectionTypePassesWithReference() + { + $container = new ContainerBuilder(); + + $container->register('foo', WaldoFoo::class); + $container->register('intersection', IntersectionConstructor::class) + ->setArguments([new Reference('foo')]); + + (new CheckTypeDeclarationsPass(true))->process($container); + + $this->addToAssertionCount(1); + } + + /** + * @requires PHP 8.1 + */ + public function testIntersectionTypeFailsWithReference() + { + $container = new ContainerBuilder(); + + $container->register('waldo', Waldo::class); + $container->register('intersection', IntersectionConstructor::class) + ->setArguments([new Reference('waldo')]); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid definition for service "intersection": argument 1 of "Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CheckTypeDeclarationsPass\\IntersectionConstructor::__construct()" accepts "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Foo&Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\WaldoInterface", "Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Waldo" passed.'); + + (new CheckTypeDeclarationsPass(true))->process($container); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PreloaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PreloaderTest.php index 5af562fcf3..98647d3268 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PreloaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PreloaderTest.php @@ -20,6 +20,9 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\Preload\D; use Symfony\Component\DependencyInjection\Tests\Fixtures\Preload\Dummy; use Symfony\Component\DependencyInjection\Tests\Fixtures\Preload\DummyWithInterface; use Symfony\Component\DependencyInjection\Tests\Fixtures\Preload\E; +use Symfony\Component\DependencyInjection\Tests\Fixtures\Preload\F; +use Symfony\Component\DependencyInjection\Tests\Fixtures\Preload\G; +use Symfony\Component\DependencyInjection\Tests\Fixtures\Preload\IntersectionDummy; use Symfony\Component\DependencyInjection\Tests\Fixtures\Preload\UnionDummy; class PreloaderTest extends TestCase @@ -72,4 +75,20 @@ class PreloaderTest extends TestCase self::assertTrue(class_exists(D::class, false)); self::assertTrue(class_exists(E::class, false)); } + + /** + * @requires PHP 8.1 + */ + public function testPreloadIntersection() + { + $r = new \ReflectionMethod(Preloader::class, 'doPreload'); + + $preloaded = []; + + $r->invokeArgs(null, ['Symfony\Component\DependencyInjection\Tests\Fixtures\Preload\IntersectionDummy', &$preloaded]); + + self::assertTrue(class_exists(IntersectionDummy::class, false)); + self::assertTrue(class_exists(F::class, false)); + self::assertTrue(class_exists(G::class, false)); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/IntersectionConstructor.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/IntersectionConstructor.php new file mode 100644 index 0000000000..2817b2effc --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/CheckTypeDeclarationsPass/IntersectionConstructor.php @@ -0,0 +1,10 @@ + + * + * 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\Preload; + +final class F +{ +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Preload/G.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Preload/G.php new file mode 100644 index 0000000000..a536856764 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Preload/G.php @@ -0,0 +1,16 @@ + + * + * 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\Preload; + +final class G +{ +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Preload/IntersectionDummy.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Preload/IntersectionDummy.php new file mode 100644 index 0000000000..b4da9404de --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Preload/IntersectionDummy.php @@ -0,0 +1,17 @@ + + * + * 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\Preload; + +final class IntersectionDummy +{ + public F&G $fg; +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php index 870f8aed0b..74b976e224 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php @@ -7,6 +7,9 @@ use Psr\Log\LoggerInterface; if (\PHP_VERSION_ID >= 80000) { require __DIR__.'/uniontype_classes.php'; } +if (\PHP_VERSION_ID >= 80100) { + require __DIR__.'/intersectiontype_classes.php'; +} class Foo { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/intersectiontype_classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/intersectiontype_classes.php new file mode 100644 index 0000000000..7331a123ad --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/intersectiontype_classes.php @@ -0,0 +1,14 @@ +