From dc55dd2c99a337d9baf803f017f139f1b280124b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 24 Sep 2017 00:04:06 +0200 Subject: [PATCH] [DI] Add AutowireRequiredMethodsPass to fix bindings for `@required` methods --- .../Compiler/AbstractRecursivePass.php | 18 +- .../Compiler/AutowirePass.php | 60 +-- .../Compiler/AutowireRequiredMethodsPass.php | 70 ++++ .../Compiler/PassConfig.php | 1 + .../Compiler/ResolveNamedArgumentsPass.php | 6 +- .../Tests/Compiler/AutowirePassTest.php | 342 +---------------- .../AutowireRequiredMethodsPassTest.php | 80 ++++ .../Compiler/ResolveBindingsPassTest.php | 16 + .../Fixtures/includes/autowiring_classes.php | 343 ++++++++++++++++++ 9 files changed, 535 insertions(+), 401 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php index bbe869b935e5c..ddc7d004da40d 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php @@ -90,7 +90,7 @@ protected function getConstructor(Definition $definition, $required) { if (is_string($factory = $definition->getFactory())) { if (!function_exists($factory)) { - throw new RuntimeException(sprintf('Unable to resolve service "%s": function "%s" does not exist.', $this->currentId, $factory)); + throw new RuntimeException(sprintf('Invalid service "%s": function "%s" does not exist.', $this->currentId, $factory)); } $r = new \ReflectionFunction($factory); if (false !== $r->getFileName() && file_exists($r->getFileName())) { @@ -108,7 +108,7 @@ protected function getConstructor(Definition $definition, $required) $class = $definition->getClass(); } if ('__construct' === $method) { - throw new RuntimeException(sprintf('Unable to resolve service "%s": "__construct()" cannot be used as a factory method.', $this->currentId)); + throw new RuntimeException(sprintf('Invalid service "%s": "__construct()" cannot be used as a factory method.', $this->currentId)); } return $this->getReflectionMethod(new Definition($class), $method); @@ -117,14 +117,14 @@ protected function getConstructor(Definition $definition, $required) $class = $definition->getClass(); if (!$r = $this->container->getReflectionClass($class)) { - throw new RuntimeException(sprintf('Unable to resolve service "%s": class "%s" does not exist.', $this->currentId, $class)); + throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class)); } if (!$r = $r->getConstructor()) { if ($required) { - throw new RuntimeException(sprintf('Unable to resolve service "%s": class%s has no constructor.', $this->currentId, sprintf($class !== $this->currentId ? ' "%s"' : '', $class))); + throw new RuntimeException(sprintf('Invalid service "%s": class%s has no constructor.', $this->currentId, sprintf($class !== $this->currentId ? ' "%s"' : '', $class))); } } elseif (!$r->isPublic()) { - throw new RuntimeException(sprintf('Unable to resolve service "%s": %s must be public.', $this->currentId, sprintf($class !== $this->currentId ? 'constructor of class "%s"' : 'its constructor', $class))); + throw new RuntimeException(sprintf('Invalid service "%s": %s must be public.', $this->currentId, sprintf($class !== $this->currentId ? 'constructor of class "%s"' : 'its constructor', $class))); } return $r; @@ -145,20 +145,20 @@ protected function getReflectionMethod(Definition $definition, $method) } if (!$class = $definition->getClass()) { - throw new RuntimeException(sprintf('Unable to resolve service "%s": the class is not set.', $this->currentId)); + throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $this->currentId)); } if (!$r = $this->container->getReflectionClass($class)) { - throw new RuntimeException(sprintf('Unable to resolve service "%s": class "%s" does not exist.', $this->currentId, $class)); + throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class)); } if (!$r->hasMethod($method)) { - throw new RuntimeException(sprintf('Unable to resolve service "%s": method "%s()" does not exist.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method)); + throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" does not exist.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method)); } $r = $r->getMethod($method); if (!$r->isPublic()) { - throw new RuntimeException(sprintf('Unable to resolve service "%s": method "%s()" must be public.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method)); + throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" must be public.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method)); } return $r; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index 8cb1978ef675d..ace116fb9b515 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -130,7 +130,6 @@ private function doProcessValue($value, $isRoot = false) return $value; } - $autowiredMethods = $this->getMethodsToAutowire($reflectionClass); $methodCalls = $value->getMethodCalls(); try { @@ -143,7 +142,7 @@ private function doProcessValue($value, $isRoot = false) array_unshift($methodCalls, array($constructor, $value->getArguments())); } - $methodCalls = $this->autowireCalls($reflectionClass, $methodCalls, $autowiredMethods); + $methodCalls = $this->autowireCalls($reflectionClass, $methodCalls); if ($constructor) { list(, $arguments) = array_shift($methodCalls); @@ -161,61 +160,18 @@ private function doProcessValue($value, $isRoot = false) } /** - * Gets the list of methods to autowire. - * * @param \ReflectionClass $reflectionClass - * - * @return \ReflectionMethod[] - */ - private function getMethodsToAutowire(\ReflectionClass $reflectionClass) - { - $methodsToAutowire = array(); - - foreach ($reflectionClass->getMethods() as $reflectionMethod) { - $r = $reflectionMethod; - - if ($r->isConstructor()) { - continue; - } - - while (true) { - if (false !== $doc = $r->getDocComment()) { - if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) { - $methodsToAutowire[strtolower($reflectionMethod->name)] = $reflectionMethod; - break; - } - if (false === stripos($doc, '@inheritdoc') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+(?:\{@inheritdoc\}|@inheritdoc)(?:\s|\*/$)#i', $doc)) { - break; - } - } - try { - $r = $r->getPrototype(); - } catch (\ReflectionException $e) { - break; // method has no prototype - } - } - } - - return $methodsToAutowire; - } - - /** - * @param \ReflectionClass $reflectionClass - * @param array $methodCalls - * @param \ReflectionMethod[] $autowiredMethods + * @param array $methodCalls * * @return array */ - private function autowireCalls(\ReflectionClass $reflectionClass, array $methodCalls, array $autowiredMethods) + private function autowireCalls(\ReflectionClass $reflectionClass, array $methodCalls) { foreach ($methodCalls as $i => $call) { list($method, $arguments) = $call; if ($method instanceof \ReflectionFunctionAbstract) { $reflectionMethod = $method; - } elseif (isset($autowiredMethods[$lcMethod = strtolower($method)]) && $autowiredMethods[$lcMethod]->isPublic()) { - $reflectionMethod = $autowiredMethods[$lcMethod]; - unset($autowiredMethods[$lcMethod]); } else { $reflectionMethod = $this->getReflectionMethod(new Definition($reflectionClass->name), $method); } @@ -227,16 +183,6 @@ private function autowireCalls(\ReflectionClass $reflectionClass, array $methodC } } - foreach ($autowiredMethods as $lcMethod => $reflectionMethod) { - $method = $reflectionMethod->name; - - if (!$reflectionMethod->isPublic()) { - $class = $reflectionClass->name; - throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": method "%s()" must be public.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method)); - } - $methodCalls[] = array($method, $this->autowireMethod($reflectionMethod, array())); - } - return $methodCalls; } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php new file mode 100644 index 0000000000000..6c744f88f6a94 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowireRequiredMethodsPass.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Definition; + +/** + * Looks for definitions with autowiring enabled and registers their corresponding "@required" methods as setters. + * + * @author Nicolas Grekas + */ +class AutowireRequiredMethodsPass extends AbstractRecursivePass +{ + /** + * {@inheritdoc} + */ + protected function processValue($value, $isRoot = false) + { + $value = parent::processValue($value, $isRoot); + + if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { + return $value; + } + if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) { + return $value; + } + + $alreadyCalledMethods = array(); + + foreach ($value->getMethodCalls() as list($method)) { + $alreadyCalledMethods[strtolower($method)] = true; + } + + foreach ($reflectionClass->getMethods() as $reflectionMethod) { + $r = $reflectionMethod; + + if ($r->isConstructor() || isset($alreadyCalledMethods[strtolower($r->name)])) { + continue; + } + + while (true) { + if (false !== $doc = $r->getDocComment()) { + if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) { + $value->addMethodCall($reflectionMethod->name); + break; + } + if (false === stripos($doc, '@inheritdoc') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+(?:\{@inheritdoc\}|@inheritdoc)(?:\s|\*/$)#i', $doc)) { + break; + } + } + try { + $r = $r->getPrototype(); + } catch (\ReflectionException $e) { + break; // method has no prototype + } + } + } + + return $value; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php index 79792bee4a106..e80b8e6cc8623 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php @@ -58,6 +58,7 @@ public function __construct() new CheckDefinitionValidityPass(), new RegisterServiceSubscribersPass(), new ResolveNamedArgumentsPass(), + new AutowireRequiredMethodsPass(), new ResolveBindingsPass(), $autowirePass = new AutowirePass(false), new ResolveServiceSubscribersPass(), diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveNamedArgumentsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveNamedArgumentsPass.php index e4d592d0c6473..a96f981d074b4 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveNamedArgumentsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveNamedArgumentsPass.php @@ -61,11 +61,11 @@ protected function processValue($value, $isRoot = false) } } - throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": method "%s()" has no argument named "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key)); + throw new InvalidArgumentException(sprintf('Invalid service "%s": method "%s()" has no argument named "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key)); } if (null !== $argument && !$argument instanceof Reference && !$argument instanceof Definition) { - throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": the value of argument "%s" of method "%s()" must be null, an instance of %s or an instance of %s, %s given.', $this->currentId, $key, $class !== $this->currentId ? $class.'::'.$method : $method, Reference::class, Definition::class, gettype($argument))); + throw new InvalidArgumentException(sprintf('Invalid service "%s": the value of argument "%s" of method "%s()" must be null, an instance of %s or an instance of %s, %s given.', $this->currentId, $key, $class !== $this->currentId ? $class.'::'.$method : $method, Reference::class, Definition::class, gettype($argument))); } foreach ($parameters as $j => $p) { @@ -76,7 +76,7 @@ protected function processValue($value, $isRoot = false) } } - throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": method "%s()" has no argument type-hinted as "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key)); + throw new InvalidArgumentException(sprintf('Invalid service "%s": method "%s()" has no argument type-hinted as "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key)); } if ($resolvedArguments !== $call[1]) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 94666df608391..dbf839b4eac83 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass; use Symfony\Component\DependencyInjection\Compiler\AutowirePass; use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -20,6 +21,8 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic; use Symfony\Component\DependencyInjection\TypedReference; +require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; + /** * @author Kévin Dunglas */ @@ -174,7 +177,7 @@ public function testExceptionsAreStored() /** * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException - * @expectedExceptionMessage Unable to resolve service "private_service": constructor of class "Symfony\Component\DependencyInjection\Tests\Compiler\PrivateConstructor" must be public. + * @expectedExceptionMessage Invalid service "private_service": constructor of class "Symfony\Component\DependencyInjection\Tests\Compiler\PrivateConstructor" must be public. */ public function testPrivateConstructorThrowsAutowireException() { @@ -554,6 +557,7 @@ public function testSetterInjection() ; (new ResolveClassPass())->process($container); + (new AutowireRequiredMethodsPass())->process($container); (new AutowirePass())->process($container); $methodCalls = $container->getDefinition('setter_injection')->getMethodCalls(); @@ -590,6 +594,7 @@ public function testExplicitMethodInjection() ; (new ResolveClassPass())->process($container); + (new AutowireRequiredMethodsPass())->process($container); (new AutowirePass())->process($container); $methodCalls = $container->getDefinition('setter_injection')->getMethodCalls(); @@ -687,6 +692,8 @@ public function testSetterInjectionCollisionThrowsException() $aDefinition = $container->register('setter_injection_collision', SetterInjectionCollision::class); $aDefinition->setAutowired(true); + (new AutowireRequiredMethodsPass())->process($container); + $pass = new AutowirePass(); $pass->process($container); } @@ -770,6 +777,7 @@ public function testNotWireableCalls($method, $expectedMsg) } (new ResolveClassPass())->process($container); + (new AutowireRequiredMethodsPass())->process($container); (new AutowirePass())->process($container); } @@ -778,7 +786,7 @@ public function provideNotWireableCalls() return array( array('setNotAutowireable', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireable()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\NotARealClass" but this class cannot be loaded.'), array('setDifferentNamespace', 'Cannot autowire service "foo": argument "$n" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setDifferentNamespace()" references class "stdClass" but no such service exists. It cannot be auto-registered because it is from a different root namespace.'), - array(null, 'Cannot autowire service "foo": method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setProtectedMethod()" must be public.'), + array(null, 'Invalid service "foo": method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setProtectedMethod()" must be public.'), ); } @@ -859,333 +867,3 @@ public function testExceptionWhenAliasDoesNotExist() $pass->process($container); } } - -class Foo -{ -} - -class Bar -{ - public function __construct(Foo $foo) - { - } -} - -interface AInterface -{ -} - -class A implements AInterface -{ - public static function create(Foo $foo) - { - } -} - -class B extends A -{ -} - -class C -{ - public function __construct(A $a) - { - } -} - -interface DInterface -{ -} - -interface EInterface extends DInterface -{ -} - -interface IInterface -{ -} - -class I implements IInterface -{ -} - -class F extends I implements EInterface -{ -} - -class G -{ - public function __construct(DInterface $d, EInterface $e, IInterface $i) - { - } -} - -class H -{ - public function __construct(B $b, DInterface $d) - { - } -} - -class D -{ - public function __construct(A $a, DInterface $d) - { - } -} - -class E -{ - public function __construct(D $d = null) - { - } -} - -class J -{ - public function __construct(I $i) - { - } -} - -interface CollisionInterface -{ -} - -class CollisionA implements CollisionInterface -{ -} - -class CollisionB implements CollisionInterface -{ -} - -class CannotBeAutowired -{ - public function __construct(CollisionInterface $collision) - { - } -} - -class CannotBeAutowiredForwardOrder -{ - public function __construct(CollisionA $a, CollisionInterface $b, CollisionB $c) - { - } -} - -class CannotBeAutowiredReverseOrder -{ - public function __construct(CollisionA $a, CollisionB $c, CollisionInterface $b) - { - } -} - -class Lille -{ -} - -class Dunglas -{ - public function __construct(Lille $l) - { - } -} - -class LesTilleuls -{ - public function __construct(Dunglas $j, Dunglas $k) - { - } -} - -class OptionalParameter -{ - public function __construct(CollisionInterface $c = null, A $a, Foo $f = null) - { - } -} - -class BadTypeHintedArgument -{ - public function __construct(Dunglas $k, NotARealClass $r) - { - } -} -class BadParentTypeHintedArgument -{ - public function __construct(Dunglas $k, OptionalServiceClass $r) - { - } -} -class NotGuessableArgument -{ - public function __construct(Foo $k) - { - } -} -class NotGuessableArgumentForSubclass -{ - public function __construct(A $k) - { - } -} -class MultipleArguments -{ - public function __construct(A $k, $foo, Dunglas $dunglas) - { - } -} - -class MultipleArgumentsOptionalScalar -{ - public function __construct(A $a, $foo = 'default_val', Lille $lille = null) - { - } -} -class MultipleArgumentsOptionalScalarLast -{ - public function __construct(A $a, Lille $lille, $foo = 'some_val') - { - } -} -class MultipleArgumentsOptionalScalarNotReallyOptional -{ - public function __construct(A $a, $foo = 'default_val', Lille $lille) - { - } -} - -/* - * Classes used for testing createResourceForClass - */ -class ClassForResource -{ - public function __construct($foo, Bar $bar = null) - { - } - - public function setBar(Bar $bar) - { - } -} -class IdenticalClassResource extends ClassForResource -{ -} - -class ClassChangedConstructorArgs extends ClassForResource -{ - public function __construct($foo, Bar $bar, $baz) - { - } -} - -class SetterInjection extends SetterInjectionParent -{ - /** - * @required - */ - public function setFoo(Foo $foo) - { - // should be called - } - - /** @inheritdoc*/ - public function setDependencies(Foo $foo, A $a) - { - // should be called - } - - /** {@inheritdoc} */ - public function setWithCallsConfigured(A $a) - { - // this method has a calls configured on it - } - - public function notASetter(A $a) - { - // should be called only when explicitly specified - } - - /** - * @required*/ - public function setChildMethodWithoutDocBlock(A $a) - { - } -} - -class SetterInjectionParent -{ - /** @required*/ - public function setDependencies(Foo $foo, A $a) - { - // should be called - } - - public function notASetter(A $a) - { - // @required should be ignored when the child does not add @inheritdoc - } - - /** @required prefix is on purpose */ - public function setWithCallsConfigured(A $a) - { - } - - /** @required */ - public function setChildMethodWithoutDocBlock(A $a) - { - } -} - -class SetterInjectionCollision -{ - /** - * @required - */ - public function setMultipleInstancesForOneArg(CollisionInterface $collision) - { - // The CollisionInterface cannot be autowired - there are multiple - - // should throw an exception - } -} - -class NotWireable -{ - public function setNotAutowireable(NotARealClass $n) - { - } - - public function setBar() - { - } - - public function setOptionalNotAutowireable(NotARealClass $n = null) - { - } - - public function setDifferentNamespace(\stdClass $n) - { - } - - public function setOptionalNoTypeHint($foo = null) - { - } - - public function setOptionalArgNoAutowireable($other = 'default_val') - { - } - - /** @required */ - protected function setProtectedMethod(A $a) - { - } -} - -class PrivateConstructor -{ - private function __construct() - { - } -} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php new file mode 100644 index 0000000000000..3b067150ff5a1 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowireRequiredMethodsPassTest.php @@ -0,0 +1,80 @@ + + * + * 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\TestCase; +use Symfony\Component\DependencyInjection\Compiler\ResolveClassPass; +use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; + +class AutowireRequiredMethodsPassTest extends TestCase +{ + public function testSetterInjection() + { + $container = new ContainerBuilder(); + $container->register(Foo::class); + $container->register(A::class); + $container->register(CollisionA::class); + $container->register(CollisionB::class); + + // manually configure *one* call, to override autowiring + $container + ->register('setter_injection', SetterInjection::class) + ->setAutowired(true) + ->addMethodCall('setWithCallsConfigured', array('manual_arg1', 'manual_arg2')); + + (new ResolveClassPass())->process($container); + (new AutowireRequiredMethodsPass())->process($container); + + $methodCalls = $container->getDefinition('setter_injection')->getMethodCalls(); + + $this->assertEquals( + array('setWithCallsConfigured', 'setFoo', 'setDependencies', 'setChildMethodWithoutDocBlock'), + array_column($methodCalls, 0) + ); + + // test setWithCallsConfigured args + $this->assertEquals( + array('manual_arg1', 'manual_arg2'), + $methodCalls[0][1] + ); + // test setFoo args + $this->assertEquals(array(), $methodCalls[1][1]); + } + + public function testExplicitMethodInjection() + { + $container = new ContainerBuilder(); + $container->register(Foo::class); + $container->register(A::class); + $container->register(CollisionA::class); + $container->register(CollisionB::class); + + $container + ->register('setter_injection', SetterInjection::class) + ->setAutowired(true) + ->addMethodCall('notASetter', array()); + + (new ResolveClassPass())->process($container); + (new AutowireRequiredMethodsPass())->process($container); + + $methodCalls = $container->getDefinition('setter_injection')->getMethodCalls(); + + $this->assertEquals( + array('notASetter', 'setFoo', 'setDependencies', 'setWithCallsConfigured', 'setChildMethodWithoutDocBlock'), + array_column($methodCalls, 0) + ); + $this->assertEquals(array(), $methodCalls[0][1]); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php index f7e29d2110ff1..16e486afafe2e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveBindingsPassTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Argument\BoundArgument; +use Symfony\Component\DependencyInjection\Compiler\AutowireRequiredMethodsPass; use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -20,6 +21,8 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass; use Symfony\Component\DependencyInjection\TypedReference; +require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php'; + class ResolveBindingsPassTest extends TestCase { public function testProcess() @@ -79,4 +82,17 @@ public function testTypedReferenceSupport() $this->assertEquals(array($typedRef), $container->getDefinition('def1')->getArguments()); $this->assertEquals(array(new Reference('foo')), $container->getDefinition('def2')->getArguments()); } + + public function testScalarSetter() + { + $container = new ContainerBuilder(); + + $definition = $container->autowire('foo', ScalarSetter::class); + $definition->setBindings(array('$defaultLocale' => 'fr')); + + (new AutowireRequiredMethodsPass())->process($container); + (new ResolveBindingsPass())->process($container); + + $this->assertEquals(array(array('setDefaultLocale', array('fr'))), $definition->getMethodCalls()); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php new file mode 100644 index 0000000000000..bf99eff6a2838 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php @@ -0,0 +1,343 @@ + prefix is on purpose */ + public function setWithCallsConfigured(A $a) + { + } + + /** @required */ + public function setChildMethodWithoutDocBlock(A $a) + { + } +} + +class NotWireable +{ + public function setNotAutowireable(NotARealClass $n) + { + } + + public function setBar() + { + } + + public function setOptionalNotAutowireable(NotARealClass $n = null) + { + } + + public function setDifferentNamespace(\stdClass $n) + { + } + + public function setOptionalNoTypeHint($foo = null) + { + } + + public function setOptionalArgNoAutowireable($other = 'default_val') + { + } + + /** @required */ + protected function setProtectedMethod(A $a) + { + } +} + +class PrivateConstructor +{ + private function __construct() + { + } +} + +class ScalarSetter +{ + /** + * @required + */ + public function setDefaultLocale($defaultLocale) + { + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy