/
githubmirror
/
symfony
Обзор
Документация
Войти
/
githubmirror
/
symfony
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
8.2
src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php
390 строк
15 KB
Nicolas Grekas
Merge branch '6.4' into 7.4
09 фев 2026, 12:33
09 фев 2026, 12:33
3780ec5
Код
Авторство
О чём код?
<?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\Compiler; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass; use Symfony\Component\DependencyInjection\Compiler\DefinitionErrorExceptionPass; use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\BarInterface; use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum; use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy; use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedEnumArgumentDummy; use Symfony\Component\DependencyInjection\Tests\Fixtures\NamedIterableArgumentDummy; use Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists; use Symfony\Component\DependencyInjection\Tests\Fixtures\UntypedWithTarget; use Symfony\Component\DependencyInjection\Tests\Fixtures\WithTarget; use Symfony\Component\DependencyInjection\TypedReference; require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; class ResolveBindingsPassTest extends TestCase { public function testProcess() { $container = new ContainerBuilder(); $bindings = [ CaseSensitiveClass::class => new BoundArgument(new Reference('foo')), 'Psr\Container\ContainerInterface $container' => new BoundArgument(new ServiceLocatorArgument([]), true, BoundArgument::INSTANCEOF_BINDING), 'iterable $objects' => new BoundArgument(new TaggedIteratorArgument('tag.name'), true, BoundArgument::INSTANCEOF_BINDING), ]; $definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class); $definition->setArguments([1 => '123']); $definition->addMethodCall('setSensitiveClass'); $definition->setBindings($bindings); $container->register('foo', CaseSensitiveClass::class) ->setBindings($bindings); $pass = new ResolveBindingsPass(); $pass->process($container); $expected = [ 0 => new Reference('foo'), 1 => '123', 3 => new ServiceLocatorArgument([]), 4 => new TaggedIteratorArgument('tag.name'), ]; $this->assertEquals($expected, $definition->getArguments()); $this->assertEquals([['setSensitiveClass', [new Reference('foo')]]], $definition->getMethodCalls()); } public function testProcessEnum() { $container = new ContainerBuilder(); $bindings = [ FooUnitEnum::class.' $bar' => new BoundArgument(FooUnitEnum::BAR), ]; $definition = $container->register(NamedEnumArgumentDummy::class, NamedEnumArgumentDummy::class); $definition->setBindings($bindings); $pass = new ResolveBindingsPass(); $pass->process($container); $expected = [FooUnitEnum::BAR]; $this->assertEquals($expected, $definition->getArguments()); } public function testUnusedBinding() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('A binding is configured for an argument named "$quz" for service "Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy", but no corresponding argument has been found. It may be unused and should be removed, or it may have a typo.'); $container = new ContainerBuilder(); $definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class); $definition->setBindings(['$quz' => '123']); $pass = new ResolveBindingsPass(); $pass->process($container); } public function testMissingParent() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('A binding is configured for an argument named "$quz" for service "Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists", but no corresponding argument has been found. It may be unused and should be removed, or it may have a typo.'); $container = new ContainerBuilder(); $definition = $container->register(ParentNotExists::class, ParentNotExists::class); $definition->setBindings(['$quz' => '123']); $pass = new ResolveBindingsPass(); $pass->process($container); } public function testTypedReferenceSupport() { $container = new ContainerBuilder(); $bindings = [ CaseSensitiveClass::class => new BoundArgument(new Reference('foo')), CaseSensitiveClass::class.' $c' => new BoundArgument(new Reference('bar')), ]; // Explicit service id $definition1 = $container->register('def1', NamedArgumentsDummy::class); $definition1->addArgument($typedRef = new TypedReference('bar', CaseSensitiveClass::class)); $definition1->setBindings($bindings); $definition2 = $container->register('def2', NamedArgumentsDummy::class); $definition2->addArgument(new TypedReference(CaseSensitiveClass::class, CaseSensitiveClass::class)); $definition2->setBindings($bindings); $definition3 = $container->register('def3', NamedArgumentsDummy::class); $definition3->addArgument(new TypedReference(CaseSensitiveClass::class, CaseSensitiveClass::class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, 'c')); $definition3->setBindings($bindings); $pass = new ResolveBindingsPass(); $pass->process($container); $this->assertEquals([$typedRef], $container->getDefinition('def1')->getArguments()); $this->assertEquals([new Reference('foo')], $container->getDefinition('def2')->getArguments()); $this->assertEquals([new Reference('bar')], $container->getDefinition('def3')->getArguments()); } public function testScalarSetterAttribute() { $container = new ContainerBuilder(); $definition = $container->autowire('foo', ScalarSetter::class); $definition->setBindings(['$defaultLocale' => 'fr']); (new AutowireRequiredMethodsPass())->process($container); (new ResolveBindingsPass())->process($container); $this->assertEquals([['setDefaultLocale', ['fr']]], $definition->getMethodCalls()); } public function testWithNonExistingSetterAndBinding() { $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Invalid service "Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy": method "setLogger()" does not exist.'); $container = new ContainerBuilder(); $bindings = [ '$c' => (new Definition('logger'))->setFactory('logger'), ]; $definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class); $definition->addMethodCall('setLogger'); $definition->setBindings($bindings); $pass = new ResolveBindingsPass(); $pass->process($container); } public function testSyntheticServiceWithBind() { $container = new ContainerBuilder(); $argument = new BoundArgument('bar'); $container->register('foo', 'stdClass') ->addArgument(new Reference('synthetic.service')); $container->register('synthetic.service') ->setSynthetic(true) ->setBindings(['$apiKey' => $argument]); $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class) ->setBindings(['$apiKey' => $argument]); (new ResolveBindingsPass())->process($container); (new DefinitionErrorExceptionPass())->process($container); $this->assertSame([1 => 'bar'], $container->getDefinition(NamedArgumentsDummy::class)->getArguments()); } public function testEmptyBindingTypehint() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Did you forget to add the type "string" to argument "$apiKey" of method "Symfony\Component\DependencyInjection\Tests\Fixtures\NamedArgumentsDummy::__construct()"?'); $container = new ContainerBuilder(); $bindings = [ 'string $apiKey' => new BoundArgument('foo'), ]; $definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class); $definition->setBindings($bindings); $pass = new ResolveBindingsPass(); $pass->process($container); } #[DataProvider('provideEmptyBindingTypehintWithMultipleBindingsCases')] public function testEmptyBindingTypehintWithMultipleBindings(string $expectedType, string $expectedArgument, string $class, array $bindings) { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage(\sprintf('Did you forget to add the type "%s" to argument "$%s"', $expectedType, $expectedArgument)); $container = new ContainerBuilder(); $definition = $container->register($class, $class); $definition->setBindings($bindings); $pass = new ResolveBindingsPass(); $pass->process($container); } public static function provideEmptyBindingTypehintWithMultipleBindingsCases(): iterable { yield 'multiple bindings with matching first' => [ 'string', 'apiKey', NamedArgumentsDummy::class, [ 'string $apiKey' => new BoundArgument('foo'), 'int $hostName' => new BoundArgument(80), ], ]; yield 'multiple bindings with matching last' => [ 'string', 'apiKey', NamedArgumentsDummy::class, [ 'int $hostName' => new BoundArgument(80), 'string $apiKey' => new BoundArgument('foo'), ], ]; yield 'three bindings with matching in the middle' => [ 'string', 'apiKey', NamedArgumentsDummy::class, [ 'bool $debug' => new BoundArgument(true), 'string $apiKey' => new BoundArgument('foo'), 'int $hostName' => new BoundArgument(80), ], ]; yield 'class type binding' => [ 'Psr\Log\LoggerInterface', 'apiKey', NamedArgumentsDummy::class, [ 'int $hostName' => new BoundArgument(80), 'Psr\Log\LoggerInterface $apiKey' => new BoundArgument(new Reference('logger')), ], ]; yield 'binding matching parameter name with #[Target] attribute' => [ 'string', 'key', UntypedWithTarget::class, [ 'int $other' => new BoundArgument(80), 'string $key' => new BoundArgument('secret'), ], ]; } public function testIterableBindingTypehint() { $autoloader = static function ($class) { if ('iterable' === $class) { throw new \RuntimeException('We should not search pseudo-type iterable as class'); } }; spl_autoload_register($autoloader); $container = new ContainerBuilder(); $definition = $container->register('bar', NamedIterableArgumentDummy::class); $definition->setBindings([ 'iterable $items' => new TaggedIteratorArgument('foo'), ]); $pass = new ResolveBindingsPass(); $pass->process($container); $this->assertInstanceOf(TaggedIteratorArgument::class, $container->getDefinition('bar')->getArgument(0)); spl_autoload_unregister($autoloader); } public function testBindWithTarget() { $container = new ContainerBuilder(); $container->register('with_target', WithTarget::class) ->setBindings([BarInterface::class.' $imageStorage' => new Reference('bar')]); (new ResolveBindingsPass())->process($container); $this->assertSame('bar', (string) $container->getDefinition('with_target')->getArgument(0)); } public function testBindWithNamedArgs() { $container = new ContainerBuilder(); $bindings = [ '$apiKey' => new BoundArgument('K'), ]; $definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class); $definition->setArguments(['c' => 'C', 'hostName' => 'H']); $definition->setBindings($bindings); $pass = new ResolveBindingsPass(); $pass->process($container); $this->assertEquals(['C', 'K', 'H'], $definition->getArguments()); } public function testAbstractArg() { $container = new ContainerBuilder(); $definition = $container->register(NamedArgumentsDummy::class, NamedArgumentsDummy::class); $definition->setArguments([new AbstractArgument(), 'apiKey' => new AbstractArgument()]); $definition->setBindings(['$c' => new BoundArgument('C'), '$apiKey' => new BoundArgument('K')]); $pass = new ResolveBindingsPass(); $pass->process($container); $this->assertEquals(['C', 'K'], $definition->getArguments()); } public function testBindingsOnTargetedArguments() { $container = new ContainerBuilder(); $container->register('service', TargetedBindingsService::class) ->setAutowired(true) ->setBindings([ '$targetName' => 'bound_via_target', '$variableName' => 'bound_via_variable', '$commonName' => 'bound_via_common_name', ]); (new ResolveBindingsPass())->process($container); $definition = $container->getDefinition('service'); // 1. Priority: Binding matches the #[Target] name $this->assertSame('bound_via_target', $definition->getArgument(0)); // 2. Fallback: Binding matches the variable name (Target name 'unusedTarget' has no binding) $this->assertSame('bound_via_variable', $definition->getArgument(1)); // 3. Equality: Target name and variable name are identical $this->assertSame('bound_via_common_name', $definition->getArgument(2)); } } class TargetedBindingsService { public function __construct( #[Target('targetName')] public $arg1, #[Target('unusedTarget')] public $variableName, #[Target('commonName')] public $commonName, ) { } }