[DI] Add AutowireRequiredMethodsPass to fix bindings for @required
methods
This commit is contained in:
parent
e84d2d0943
commit
dc55dd2c99
@ -90,7 +90,7 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
|
||||
{
|
||||
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 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
|
||||
$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 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
|
||||
$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 @@ abstract class AbstractRecursivePass implements CompilerPassInterface
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -130,7 +130,6 @@ class AutowirePass extends AbstractRecursivePass
|
||||
return $value;
|
||||
}
|
||||
|
||||
$autowiredMethods = $this->getMethodsToAutowire($reflectionClass);
|
||||
$methodCalls = $value->getMethodCalls();
|
||||
|
||||
try {
|
||||
@ -143,7 +142,7 @@ class AutowirePass extends AbstractRecursivePass
|
||||
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 @@ class AutowirePass extends AbstractRecursivePass
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ class AutowirePass extends AbstractRecursivePass
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,70 @@
|
||||
<?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\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
|
||||
/**
|
||||
* Looks for definitions with autowiring enabled and registers their corresponding "@required" methods as setters.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -58,6 +58,7 @@ class PassConfig
|
||||
new CheckDefinitionValidityPass(),
|
||||
new RegisterServiceSubscribersPass(),
|
||||
new ResolveNamedArgumentsPass(),
|
||||
new AutowireRequiredMethodsPass(),
|
||||
new ResolveBindingsPass(),
|
||||
$autowirePass = new AutowirePass(false),
|
||||
new ResolveServiceSubscribersPass(),
|
||||
|
@ -61,11 +61,11 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass
|
||||
}
|
||||
}
|
||||
|
||||
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]) {
|
||||
|
@ -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\Reference;
|
||||
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 <dunglas@gmail.com>
|
||||
*/
|
||||
@ -174,7 +177,7 @@ class AutowirePassTest extends TestCase
|
||||
|
||||
/**
|
||||
* @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 @@ class AutowirePassTest extends TestCase
|
||||
;
|
||||
|
||||
(new ResolveClassPass())->process($container);
|
||||
(new AutowireRequiredMethodsPass())->process($container);
|
||||
(new AutowirePass())->process($container);
|
||||
|
||||
$methodCalls = $container->getDefinition('setter_injection')->getMethodCalls();
|
||||
@ -590,6 +594,7 @@ class AutowirePassTest extends TestCase
|
||||
;
|
||||
|
||||
(new ResolveClassPass())->process($container);
|
||||
(new AutowireRequiredMethodsPass())->process($container);
|
||||
(new AutowirePass())->process($container);
|
||||
|
||||
$methodCalls = $container->getDefinition('setter_injection')->getMethodCalls();
|
||||
@ -687,6 +692,8 @@ class AutowirePassTest extends TestCase
|
||||
$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 @@ class AutowirePassTest extends TestCase
|
||||
}
|
||||
|
||||
(new ResolveClassPass())->process($container);
|
||||
(new AutowireRequiredMethodsPass())->process($container);
|
||||
(new AutowirePass())->process($container);
|
||||
}
|
||||
|
||||
@ -778,7 +786,7 @@ class AutowirePassTest extends TestCase
|
||||
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 @@ class AutowirePassTest extends TestCase
|
||||
$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 <tab> 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,80 @@
|
||||
<?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\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]);
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler;
|
||||
|
||||
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\NamedArgumentsDummy;
|
||||
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 @@ class ResolveBindingsPassTest extends TestCase
|
||||
$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());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,343 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
|
||||
|
||||
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 SetterInjectionCollision
|
||||
{
|
||||
/**
|
||||
* @required
|
||||
*/
|
||||
public function setMultipleInstancesForOneArg(CollisionInterface $collision)
|
||||
{
|
||||
// The CollisionInterface cannot be autowired - there are multiple
|
||||
|
||||
// should throw an exception
|
||||
}
|
||||
}
|
||||
|
||||
class SetterInjection extends SetterInjectionParent
|
||||
{
|
||||
/**
|
||||
* @required
|
||||
*/
|
||||
public function setFoo(Foo $foo)
|
||||
{
|
||||
// should be called
|
||||
}
|
||||
|
||||
/** @inheritdoc*/ // <- brackets are missing on purpose
|
||||
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 <tab> 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)
|
||||
{
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user