[DependencyInjection] Support for intersection types
Signed-off-by: Alexander M. Turek <me@derrabus.de>
This commit is contained in:
parent
3b21091cce
commit
d28cf24258
2
.github/patch-types.php
vendored
2
.github/patch-types.php
vendored
@ -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/Debug/Tests/Fixtures/'):
|
||||||
case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Compiler/OptionalServiceClass.php'):
|
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/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/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/ParentNotExists.php'):
|
||||||
case false !== strpos($file, '/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Preload/'):
|
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'):
|
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);
|
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) {
|
if (!$reflectionType instanceof \ReflectionNamedType) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ class Preloader
|
|||||||
return;
|
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()) {
|
if (!$t->isBuiltin()) {
|
||||||
self::doPreload($t instanceof \ReflectionNamedType ? $t->getName() : $t, $preloaded);
|
self::doPreload($t instanceof \ReflectionNamedType ? $t->getName() : $t, $preloaded);
|
||||||
}
|
}
|
||||||
|
@ -33,22 +33,31 @@ class ProxyHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
$types = [];
|
$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) {
|
foreach ($reflectionTypes as $type) {
|
||||||
$name = $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type;
|
|
||||||
|
|
||||||
if ($type->isBuiltin()) {
|
if ($type->isBuiltin()) {
|
||||||
if (!$noBuiltin) {
|
if (!$noBuiltin) {
|
||||||
$types[] = $name;
|
$types[] = $type->getName();
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$lcName = strtolower($name);
|
$lcName = strtolower($type->getName());
|
||||||
$prefix = $noBuiltin ? '' : '\\';
|
$prefix = $noBuiltin ? '' : '\\';
|
||||||
|
|
||||||
if ('self' !== $lcName && 'parent' !== $lcName) {
|
if ('self' !== $lcName && 'parent' !== $lcName) {
|
||||||
$types[] = '' !== $prefix ? $prefix.$name : $name;
|
$types[] = $prefix.$type->getName();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!$r instanceof \ReflectionMethod) {
|
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);
|
$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()
|
public function testTypeNotGuessableWithTypeSet()
|
||||||
{
|
{
|
||||||
$container = new ContainerBuilder();
|
$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\Deprecated;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Foo;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Foo;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\FooObject;
|
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\UnionConstructor;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Waldo;
|
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\DependencyInjection\Tests\Fixtures\CheckTypeDeclarationsPass\Wobble;
|
||||||
use Symfony\Component\ExpressionLanguage\Expression;
|
use Symfony\Component\ExpressionLanguage\Expression;
|
||||||
|
|
||||||
@ -953,4 +955,37 @@ class CheckTypeDeclarationsPassTest extends TestCase
|
|||||||
|
|
||||||
$this->addToAssertionCount(1);
|
$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\Dummy;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\Preload\DummyWithInterface;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\Preload\DummyWithInterface;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\Preload\E;
|
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;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\Preload\UnionDummy;
|
||||||
|
|
||||||
class PreloaderTest extends TestCase
|
class PreloaderTest extends TestCase
|
||||||
@ -72,4 +75,20 @@ class PreloaderTest extends TestCase
|
|||||||
self::assertTrue(class_exists(D::class, false));
|
self::assertTrue(class_exists(D::class, false));
|
||||||
self::assertTrue(class_exists(E::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) {
|
if (\PHP_VERSION_ID >= 80000) {
|
||||||
require __DIR__.'/uniontype_classes.php';
|
require __DIR__.'/uniontype_classes.php';
|
||||||
}
|
}
|
||||||
|
if (\PHP_VERSION_ID >= 80100) {
|
||||||
|
require __DIR__.'/intersectiontype_classes.php';
|
||||||
|
}
|
||||||
|
|
||||||
class Foo
|
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
Block a user