bug #42097 [DependencyInjection] Support for intersection types (derrabus)
This PR was merged into the 4.4 branch.
Discussion
----------
[DependencyInjection] Support for intersection types
| Q | A
| ------------- | ---
| Branch? | 4.4
| Bug fix? | yes
| New feature? | no
| Deprecations? | no
| Tickets | #41552
| License | MIT
| Doc PR | N/A
Commits
-------
d28cf24258
[DependencyInjection] Support for intersection types
This commit is contained in:
commit
c9e4c33c0f
|
@ -32,7 +32,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'):
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass;
|
||||
|
||||
class IntersectionConstructor
|
||||
{
|
||||
public function __construct(Foo&WaldoInterface $arg)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass;
|
||||
|
||||
class WaldoFoo extends Foo implements WaldoInterface
|
||||
{
|
||||
}
|
|
@ -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
|
||||
{
|
||||
}
|
|
@ -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
|
||||
{
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
|
||||
|
||||
interface AnotherInterface
|
||||
{
|
||||
}
|
||||
|
||||
class IntersectionClasses
|
||||
{
|
||||
public function __construct(CollisionInterface&AnotherInterface $collision)
|
||||
{
|
||||
}
|
||||
}
|
Reference in New Issue