[DependencyInjection] Support for intersection types

Signed-off-by: Alexander M. Turek <me@derrabus.de>
This commit is contained in:
Alexander M. Turek 2021-07-14 14:13:59 +02:00
parent 3b21091cce
commit d28cf24258
14 changed files with 182 additions and 8 deletions

View File

@ -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'):

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass;
class IntersectionConstructor
{
public function __construct(Foo&WaldoInterface $arg)
{
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass;
class WaldoFoo extends Foo implements WaldoInterface
{
}

View File

@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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
{
}

View File

@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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
{
}

View File

@ -0,0 +1,17 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}

View File

@ -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
{

View File

@ -0,0 +1,14 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
interface AnotherInterface
{
}
class IntersectionClasses
{
public function __construct(CollisionInterface&AnotherInterface $collision)
{
}
}