feature #20167 [DependencyInjection] Make method (setter) autowiring configurable (dunglas)
This PR was merged into the 3.3-dev branch.
Discussion
----------
[DependencyInjection] Make method (setter) autowiring configurable
| Q | A |
| --- | --- |
| Branch? | master |
| Bug fix? | no |
| New feature? | yes |
| BC breaks? | no |
| Deprecations? | maybe? |
| Tests pass? | yes |
| Fixed tickets | #19631 |
| License | MIT |
| Doc PR | symfony/symfony-docs#7041 |
Follow up of #19631. Implements https://github.com/symfony/symfony/pull/19631#issuecomment-240646169:
Edit: the last supported format:
``` yaml
services:
foo:
class: Foo
autowire: ['__construct', 'set*'] # Autowire constructor and all setters
autowire: true # Converted by loaders in `autowire: ['__construct']` for BC
autowire: ['foo', 'bar'] # Autowire only `foo` and `bar` methods
```
Outdated:
``` yaml
autowire: true # constructor autowiring
autowire: [__construct, setFoo, setBar] # autowire whitelisted methods only
autowire: '*' # autowire constructor + every setters (following existing rules for setters autowiring)
```
- [x] Allow to specify the list of methods in the XML loader
- [x] Add tests for the YAML loader
Commits
-------
6dd53c7
[DependencyInjection] Introduce method injection for autowiring
This commit is contained in:
commit
69dcf41a3c
@ -24,6 +24,9 @@ use Symfony\Component\DependencyInjection\Reference;
|
|||||||
*/
|
*/
|
||||||
class AutowirePass implements CompilerPassInterface
|
class AutowirePass implements CompilerPassInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var ContainerBuilder
|
||||||
|
*/
|
||||||
private $container;
|
private $container;
|
||||||
private $reflectionClasses = array();
|
private $reflectionClasses = array();
|
||||||
private $definedTypes = array();
|
private $definedTypes = array();
|
||||||
@ -41,8 +44,8 @@ class AutowirePass implements CompilerPassInterface
|
|||||||
try {
|
try {
|
||||||
$this->container = $container;
|
$this->container = $container;
|
||||||
foreach ($container->getDefinitions() as $id => $definition) {
|
foreach ($container->getDefinitions() as $id => $definition) {
|
||||||
if ($definition->isAutowired()) {
|
if ($autowiredMethods = $definition->getAutowiredMethods()) {
|
||||||
$this->completeDefinition($id, $definition);
|
$this->completeDefinition($id, $definition, $autowiredMethods);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -72,8 +75,10 @@ class AutowirePass implements CompilerPassInterface
|
|||||||
$metadata['__construct'] = self::getResourceMetadataForMethod($constructor);
|
$metadata['__construct'] = self::getResourceMetadataForMethod($constructor);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (self::getSetters($reflectionClass) as $reflectionMethod) {
|
foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
|
||||||
$metadata[$reflectionMethod->name] = self::getResourceMetadataForMethod($reflectionMethod);
|
if (!$reflectionMethod->isStatic()) {
|
||||||
|
$metadata[$reflectionMethod->name] = self::getResourceMetadataForMethod($reflectionMethod);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AutowireServiceResource($reflectionClass->name, $reflectionClass->getFileName(), $metadata);
|
return new AutowireServiceResource($reflectionClass->name, $reflectionClass->getFileName(), $metadata);
|
||||||
@ -84,10 +89,11 @@ class AutowirePass implements CompilerPassInterface
|
|||||||
*
|
*
|
||||||
* @param string $id
|
* @param string $id
|
||||||
* @param Definition $definition
|
* @param Definition $definition
|
||||||
|
* @param string[] $autowiredMethods
|
||||||
*
|
*
|
||||||
* @throws RuntimeException
|
* @throws RuntimeException
|
||||||
*/
|
*/
|
||||||
private function completeDefinition($id, Definition $definition)
|
private function completeDefinition($id, Definition $definition, array $autowiredMethods)
|
||||||
{
|
{
|
||||||
if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
|
if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
|
||||||
return;
|
return;
|
||||||
@ -97,12 +103,75 @@ class AutowirePass implements CompilerPassInterface
|
|||||||
$this->container->addResource(static::createResourceForClass($reflectionClass));
|
$this->container->addResource(static::createResourceForClass($reflectionClass));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$constructor = $reflectionClass->getConstructor()) {
|
$methodsCalled = array();
|
||||||
return;
|
foreach ($definition->getMethodCalls() as $methodCall) {
|
||||||
|
$methodsCalled[$methodCall[0]] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$arguments = $definition->getArguments();
|
foreach ($this->getMethodsToAutowire($id, $reflectionClass, $autowiredMethods) as $reflectionMethod) {
|
||||||
foreach ($constructor->getParameters() as $index => $parameter) {
|
if (!isset($methodsCalled[$reflectionMethod->name])) {
|
||||||
|
$this->autowireMethod($id, $definition, $reflectionMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of methods to autowire.
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
* @param \ReflectionClass $reflectionClass
|
||||||
|
* @param string[] $autowiredMethods
|
||||||
|
*
|
||||||
|
* @return \ReflectionMethod[]
|
||||||
|
*/
|
||||||
|
private function getMethodsToAutowire($id, \ReflectionClass $reflectionClass, array $autowiredMethods)
|
||||||
|
{
|
||||||
|
$found = array();
|
||||||
|
$regexList = array();
|
||||||
|
foreach ($autowiredMethods as $pattern) {
|
||||||
|
$regexList[] = '/^'.str_replace('\*', '.*', preg_quote($pattern, '/')).'$/i';
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
|
||||||
|
if ($reflectionMethod->isStatic()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($regexList as $k => $regex) {
|
||||||
|
if (preg_match($regex, $reflectionMethod->name)) {
|
||||||
|
$found[] = $autowiredMethods[$k];
|
||||||
|
yield $reflectionMethod;
|
||||||
|
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($notFound = array_diff($autowiredMethods, $found)) {
|
||||||
|
$compiler = $this->container->getCompiler();
|
||||||
|
$compiler->addLogMessage($compiler->getLoggingFormatter()->formatUnusedAutowiringPatterns($this, $id, $notFound));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Autowires the constructor or a setter.
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
* @param Definition $definition
|
||||||
|
* @param \ReflectionMethod $reflectionMethod
|
||||||
|
*
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
private function autowireMethod($id, Definition $definition, \ReflectionMethod $reflectionMethod)
|
||||||
|
{
|
||||||
|
if ($isConstructor = $reflectionMethod->isConstructor()) {
|
||||||
|
$arguments = $definition->getArguments();
|
||||||
|
} else {
|
||||||
|
$arguments = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$addMethodCall = false; // Whether the method should be added to the definition as a call or as arguments
|
||||||
|
foreach ($reflectionMethod->getParameters() as $index => $parameter) {
|
||||||
if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
|
if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -111,7 +180,11 @@ class AutowirePass implements CompilerPassInterface
|
|||||||
if (!$typeHint = $parameter->getClass()) {
|
if (!$typeHint = $parameter->getClass()) {
|
||||||
// no default value? Then fail
|
// no default value? Then fail
|
||||||
if (!$parameter->isOptional()) {
|
if (!$parameter->isOptional()) {
|
||||||
throw new RuntimeException(sprintf('Unable to autowire argument index %d ($%s) for the service "%s". If this is an object, give it a type-hint. Otherwise, specify this argument\'s value explicitly.', $index, $parameter->name, $id));
|
if ($isConstructor) {
|
||||||
|
throw new RuntimeException(sprintf('Unable to autowire argument index %d ($%s) for the service "%s". If this is an object, give it a type-hint. Otherwise, specify this argument\'s value explicitly.', $index, $parameter->name, $id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// specifically pass the default value
|
// specifically pass the default value
|
||||||
@ -126,16 +199,23 @@ class AutowirePass implements CompilerPassInterface
|
|||||||
|
|
||||||
if (isset($this->types[$typeHint->name])) {
|
if (isset($this->types[$typeHint->name])) {
|
||||||
$value = new Reference($this->types[$typeHint->name]);
|
$value = new Reference($this->types[$typeHint->name]);
|
||||||
|
$addMethodCall = true;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
$value = $this->createAutowiredDefinition($typeHint, $id);
|
$value = $this->createAutowiredDefinition($typeHint, $id);
|
||||||
|
$addMethodCall = true;
|
||||||
} catch (RuntimeException $e) {
|
} catch (RuntimeException $e) {
|
||||||
if ($parameter->allowsNull()) {
|
if ($parameter->allowsNull()) {
|
||||||
$value = null;
|
$value = null;
|
||||||
} elseif ($parameter->isDefaultValueAvailable()) {
|
} elseif ($parameter->isDefaultValueAvailable()) {
|
||||||
$value = $parameter->getDefaultValue();
|
$value = $parameter->getDefaultValue();
|
||||||
} else {
|
} else {
|
||||||
throw $e;
|
// The exception code is set to 1 if the exception must be thrown even if it's a setter
|
||||||
|
if (1 === $e->getCode() || $isConstructor) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,7 +223,11 @@ class AutowirePass implements CompilerPassInterface
|
|||||||
// Typehint against a non-existing class
|
// Typehint against a non-existing class
|
||||||
|
|
||||||
if (!$parameter->isDefaultValueAvailable()) {
|
if (!$parameter->isDefaultValueAvailable()) {
|
||||||
throw new RuntimeException(sprintf('Cannot autowire argument %s for %s because the type-hinted class does not exist (%s).', $index + 1, $definition->getClass(), $e->getMessage()), 0, $e);
|
if ($isConstructor) {
|
||||||
|
throw new RuntimeException(sprintf('Cannot autowire argument %s for %s because the type-hinted class does not exist (%s).', $index + 1, $definition->getClass(), $e->getMessage()), 0, $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = $parameter->getDefaultValue();
|
$value = $parameter->getDefaultValue();
|
||||||
@ -155,7 +239,12 @@ class AutowirePass implements CompilerPassInterface
|
|||||||
// it's possible index 1 was set, then index 0, then 2, etc
|
// it's possible index 1 was set, then index 0, then 2, etc
|
||||||
// make sure that we re-order so they're injected as expected
|
// make sure that we re-order so they're injected as expected
|
||||||
ksort($arguments);
|
ksort($arguments);
|
||||||
$definition->setArguments($arguments);
|
|
||||||
|
if ($isConstructor) {
|
||||||
|
$definition->setArguments($arguments);
|
||||||
|
} elseif ($addMethodCall) {
|
||||||
|
$definition->addMethodCall($reflectionMethod->name, $arguments);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -253,7 +342,7 @@ class AutowirePass implements CompilerPassInterface
|
|||||||
$classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
|
$classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
|
||||||
$matchingServices = implode(', ', $this->ambiguousServiceTypes[$typeHint->name]);
|
$matchingServices = implode(', ', $this->ambiguousServiceTypes[$typeHint->name]);
|
||||||
|
|
||||||
throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $typeHint->name, $id, $classOrInterface, $matchingServices));
|
throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $typeHint->name, $id, $classOrInterface, $matchingServices), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$typeHint->isInstantiable()) {
|
if (!$typeHint->isInstantiable()) {
|
||||||
@ -269,7 +358,7 @@ class AutowirePass implements CompilerPassInterface
|
|||||||
$this->populateAvailableType($argumentId, $argumentDefinition);
|
$this->populateAvailableType($argumentId, $argumentDefinition);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->completeDefinition($argumentId, $argumentDefinition);
|
$this->completeDefinition($argumentId, $argumentDefinition, array('__construct'));
|
||||||
} catch (RuntimeException $e) {
|
} catch (RuntimeException $e) {
|
||||||
$classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
|
$classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
|
||||||
$message = sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeHint->name, $id, $classOrInterface);
|
$message = sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeHint->name, $id, $classOrInterface);
|
||||||
@ -320,20 +409,6 @@ class AutowirePass implements CompilerPassInterface
|
|||||||
$this->ambiguousServiceTypes[$type][] = $id;
|
$this->ambiguousServiceTypes[$type][] = $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param \ReflectionClass $reflectionClass
|
|
||||||
*
|
|
||||||
* @return \ReflectionMethod[]
|
|
||||||
*/
|
|
||||||
private static function getSetters(\ReflectionClass $reflectionClass)
|
|
||||||
{
|
|
||||||
foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
|
|
||||||
if (!$reflectionMethod->isStatic() && 1 === $reflectionMethod->getNumberOfParameters() && 0 === strpos($reflectionMethod->name, 'set')) {
|
|
||||||
yield $reflectionMethod;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function getResourceMetadataForMethod(\ReflectionMethod $method)
|
private static function getResourceMetadataForMethod(\ReflectionMethod $method)
|
||||||
{
|
{
|
||||||
$methodArgumentsMetadata = array();
|
$methodArgumentsMetadata = array();
|
||||||
|
@ -38,6 +38,11 @@ class LoggingFormatter
|
|||||||
return $this->format($pass, sprintf('Resolving inheritance for "%s" (parent: %s).', $childId, $parentId));
|
return $this->format($pass, sprintf('Resolving inheritance for "%s" (parent: %s).', $childId, $parentId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function formatUnusedAutowiringPatterns(CompilerPassInterface $pass, $id, array $patterns)
|
||||||
|
{
|
||||||
|
return $this->format($pass, sprintf('Autowiring\'s patterns "%s" for service "%s" don\'t match any method.', implode('", "', $patterns), $id));
|
||||||
|
}
|
||||||
|
|
||||||
public function format(CompilerPassInterface $pass, $message)
|
public function format(CompilerPassInterface $pass, $message)
|
||||||
{
|
{
|
||||||
return sprintf('%s: %s', get_class($pass), $message);
|
return sprintf('%s: %s', get_class($pass), $message);
|
||||||
|
@ -36,7 +36,7 @@ class Definition
|
|||||||
private $abstract = false;
|
private $abstract = false;
|
||||||
private $lazy = false;
|
private $lazy = false;
|
||||||
private $decoratedService;
|
private $decoratedService;
|
||||||
private $autowired = false;
|
private $autowiredMethods = array();
|
||||||
private $autowiringTypes = array();
|
private $autowiringTypes = array();
|
||||||
|
|
||||||
protected $arguments;
|
protected $arguments;
|
||||||
@ -662,19 +662,50 @@ class Definition
|
|||||||
*/
|
*/
|
||||||
public function isAutowired()
|
public function isAutowired()
|
||||||
{
|
{
|
||||||
return $this->autowired;
|
return !empty($this->autowiredMethods);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets autowired methods.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getAutowiredMethods()
|
||||||
|
{
|
||||||
|
return $this->autowiredMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets autowired.
|
* Sets autowired.
|
||||||
*
|
*
|
||||||
|
* Allowed values:
|
||||||
|
* - true: constructor autowiring, same as $this->setAutowiredMethods(array('__construct'))
|
||||||
|
* - false: no autowiring, same as $this->setAutowiredMethods(array())
|
||||||
|
*
|
||||||
* @param bool $autowired
|
* @param bool $autowired
|
||||||
*
|
*
|
||||||
* @return Definition The current instance
|
* @return Definition The current instance
|
||||||
*/
|
*/
|
||||||
public function setAutowired($autowired)
|
public function setAutowired($autowired)
|
||||||
{
|
{
|
||||||
$this->autowired = $autowired;
|
$this->autowiredMethods = $autowired ? array('__construct') : array();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets autowired methods.
|
||||||
|
*
|
||||||
|
* Example of allowed value:
|
||||||
|
* - array('__construct', 'set*', 'initialize'): autowire whitelisted methods only
|
||||||
|
*
|
||||||
|
* @param string[] $autowiredMethods
|
||||||
|
*
|
||||||
|
* @return Definition The current instance
|
||||||
|
*/
|
||||||
|
public function setAutowiredMethods(array $autowiredMethods)
|
||||||
|
{
|
||||||
|
$this->autowiredMethods = $autowiredMethods;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -239,6 +239,19 @@ class XmlFileLoader extends FileLoader
|
|||||||
$definition->addAutowiringType($type->textContent);
|
$definition->addAutowiringType($type->textContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$autowireTags = array();
|
||||||
|
foreach ($this->getChildren($service, 'autowire') as $type) {
|
||||||
|
$autowireTags[] = $type->textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($autowireTags) {
|
||||||
|
if ($service->hasAttribute('autowire')) {
|
||||||
|
throw new InvalidArgumentException(sprintf('The "autowire" attribute cannot be used together with "<autowire>" tags for service "%s" in %s.', (string) $service->getAttribute('id'), $file));
|
||||||
|
}
|
||||||
|
|
||||||
|
$definition->setAutowiredMethods($autowireTags);
|
||||||
|
}
|
||||||
|
|
||||||
if ($value = $service->getAttribute('decorates')) {
|
if ($value = $service->getAttribute('decorates')) {
|
||||||
$renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null;
|
$renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null;
|
||||||
$priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0;
|
$priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0;
|
||||||
|
@ -302,7 +302,11 @@ class YamlFileLoader extends FileLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($service['autowire'])) {
|
if (isset($service['autowire'])) {
|
||||||
$definition->setAutowired($service['autowire']);
|
if (is_array($service['autowire'])) {
|
||||||
|
$definition->setAutowiredMethods($service['autowire']);
|
||||||
|
} else {
|
||||||
|
$definition->setAutowired($service['autowire']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($service['autowiring_types'])) {
|
if (isset($service['autowiring_types'])) {
|
||||||
|
@ -100,6 +100,7 @@
|
|||||||
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
|
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
|
||||||
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
|
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
|
||||||
<xsd:element name="autowiring-type" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
<xsd:element name="autowiring-type" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
||||||
|
<xsd:element name="autowire" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
|
||||||
</xsd:choice>
|
</xsd:choice>
|
||||||
<xsd:attribute name="id" type="xsd:string" />
|
<xsd:attribute name="id" type="xsd:string" />
|
||||||
<xsd:attribute name="class" type="xsd:string" />
|
<xsd:attribute name="class" type="xsd:string" />
|
||||||
|
@ -429,6 +429,74 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSetterInjection()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register('app_foo', Foo::class);
|
||||||
|
$container->register('app_a', A::class);
|
||||||
|
$container->register('app_collision_a', CollisionA::class);
|
||||||
|
$container->register('app_collision_b', CollisionB::class);
|
||||||
|
|
||||||
|
// manually configure *one* call, to override autowiring
|
||||||
|
$container
|
||||||
|
->register('setter_injection', SetterInjection::class)
|
||||||
|
->setAutowiredMethods(array('__construct', 'set*'))
|
||||||
|
->addMethodCall('setWithCallsConfigured', array('manual_arg1', 'manual_arg2'))
|
||||||
|
;
|
||||||
|
|
||||||
|
$pass = new AutowirePass();
|
||||||
|
$pass->process($container);
|
||||||
|
|
||||||
|
$methodCalls = $container->getDefinition('setter_injection')->getMethodCalls();
|
||||||
|
|
||||||
|
// grab the call method names
|
||||||
|
$actualMethodNameCalls = array_map(function ($call) {
|
||||||
|
return $call[0];
|
||||||
|
}, $methodCalls);
|
||||||
|
$this->assertEquals(
|
||||||
|
array('setWithCallsConfigured', 'setFoo', 'setDependencies'),
|
||||||
|
$actualMethodNameCalls
|
||||||
|
);
|
||||||
|
|
||||||
|
// test setWithCallsConfigured args
|
||||||
|
$this->assertEquals(
|
||||||
|
array('manual_arg1', 'manual_arg2'),
|
||||||
|
$methodCalls[0][1]
|
||||||
|
);
|
||||||
|
// test setFoo args
|
||||||
|
$this->assertEquals(
|
||||||
|
array(new Reference('app_foo')),
|
||||||
|
$methodCalls[1][1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExplicitMethodInjection()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register('app_foo', Foo::class);
|
||||||
|
$container->register('app_a', A::class);
|
||||||
|
$container->register('app_collision_a', CollisionA::class);
|
||||||
|
$container->register('app_collision_b', CollisionB::class);
|
||||||
|
|
||||||
|
$container
|
||||||
|
->register('setter_injection', SetterInjection::class)
|
||||||
|
->setAutowiredMethods(array('setFoo', 'notASetter'))
|
||||||
|
;
|
||||||
|
|
||||||
|
$pass = new AutowirePass();
|
||||||
|
$pass->process($container);
|
||||||
|
|
||||||
|
$methodCalls = $container->getDefinition('setter_injection')->getMethodCalls();
|
||||||
|
|
||||||
|
$actualMethodNameCalls = array_map(function ($call) {
|
||||||
|
return $call[0];
|
||||||
|
}, $methodCalls);
|
||||||
|
$this->assertEquals(
|
||||||
|
array('setFoo', 'notASetter'),
|
||||||
|
$actualMethodNameCalls
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider getCreateResourceTests
|
* @dataProvider getCreateResourceTests
|
||||||
*/
|
*/
|
||||||
@ -476,6 +544,37 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
$this->assertTrue($container->hasDefinition('bar'));
|
$this->assertTrue($container->hasDefinition('bar'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
||||||
|
* @expectedExceptionMessage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" for the service "setter_injection_collision". Multiple services exist for this interface (c1, c2).
|
||||||
|
* @expectedExceptionCode 1
|
||||||
|
*/
|
||||||
|
public function testSetterInjectionCollisionThrowsException()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
|
||||||
|
$container->register('c1', CollisionA::class);
|
||||||
|
$container->register('c2', CollisionB::class);
|
||||||
|
$aDefinition = $container->register('setter_injection_collision', SetterInjectionCollision::class);
|
||||||
|
$aDefinition->setAutowiredMethods(array('__construct', 'set*'));
|
||||||
|
|
||||||
|
$pass = new AutowirePass();
|
||||||
|
$pass->process($container);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLogUnusedPatterns()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
|
||||||
|
$definition = $container->register('foo', Foo::class);
|
||||||
|
$definition->setAutowiredMethods(array('not', 'exist*'));
|
||||||
|
|
||||||
|
$pass = new AutowirePass();
|
||||||
|
$pass->process($container);
|
||||||
|
|
||||||
|
$this->assertEquals(array(AutowirePass::class.': Autowiring\'s patterns "not", "exist*" for service "foo" don\'t match any method.'), $container->getCompiler()->getLog());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Foo
|
class Foo
|
||||||
@ -648,9 +747,74 @@ class ClassForResource
|
|||||||
class IdenticalClassResource extends ClassForResource
|
class IdenticalClassResource extends ClassForResource
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClassChangedConstructorArgs extends ClassForResource
|
class ClassChangedConstructorArgs extends ClassForResource
|
||||||
{
|
{
|
||||||
public function __construct($foo, Bar $bar, $baz)
|
public function __construct($foo, Bar $bar, $baz)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SetterInjection
|
||||||
|
{
|
||||||
|
public function setFoo(Foo $foo)
|
||||||
|
{
|
||||||
|
// should be called
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDependencies(Foo $foo, A $a)
|
||||||
|
{
|
||||||
|
// should be called
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBar()
|
||||||
|
{
|
||||||
|
// should not be called
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNotAutowireable(NotARealClass $n)
|
||||||
|
{
|
||||||
|
// should not be called
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setArgCannotAutowire($foo)
|
||||||
|
{
|
||||||
|
// should not be called
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOptionalNotAutowireable(NotARealClass $n = null)
|
||||||
|
{
|
||||||
|
// should not be called
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOptionalNoTypeHint($foo = null)
|
||||||
|
{
|
||||||
|
// should not be called
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOptionalArgNoAutowireable($other = 'default_val')
|
||||||
|
{
|
||||||
|
// should not be called
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setWithCallsConfigured(A $a)
|
||||||
|
{
|
||||||
|
// this method has a calls configured on it
|
||||||
|
// should not be called
|
||||||
|
}
|
||||||
|
|
||||||
|
public function notASetter(A $a)
|
||||||
|
{
|
||||||
|
// should be called only when explicitly specified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SetterInjectionCollision
|
||||||
|
{
|
||||||
|
public function setMultipleInstancesForOneArg(CollisionInterface $collision)
|
||||||
|
{
|
||||||
|
// The CollisionInterface cannot be autowired - there are multiple
|
||||||
|
|
||||||
|
// should throw an exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -290,6 +290,16 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertFalse($def->isAutowired());
|
$this->assertFalse($def->isAutowired());
|
||||||
$def->setAutowired(true);
|
$def->setAutowired(true);
|
||||||
$this->assertTrue($def->isAutowired());
|
$this->assertTrue($def->isAutowired());
|
||||||
|
$this->assertEquals(array('__construct'), $def->getAutowiredMethods());
|
||||||
|
|
||||||
|
$def->setAutowiredMethods(array('foo'));
|
||||||
|
$def->setAutowired(false);
|
||||||
|
$this->assertSame(array(), $def->getAutowiredMethods());
|
||||||
|
$this->assertFalse($def->isAutowired());
|
||||||
|
|
||||||
|
$def->setAutowiredMethods(array('getFoo', 'getBar'));
|
||||||
|
$this->assertEquals(array('getFoo', 'getBar'), $def->getAutowiredMethods());
|
||||||
|
$this->assertTrue($def->isAutowired());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTypes()
|
public function testTypes()
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||||
|
<services>
|
||||||
|
<service id="autowire_array" class="Foo">
|
||||||
|
<autowire>set*</autowire>
|
||||||
|
<autowire>bar</autowire>
|
||||||
|
</service>
|
||||||
|
</services>
|
||||||
|
</container>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||||
|
<services>
|
||||||
|
<service id="autowire_array" class="Foo" autowire="*">
|
||||||
|
<autowire>setFoo</autowire>
|
||||||
|
</service>
|
||||||
|
</services>
|
||||||
|
</container>
|
@ -0,0 +1,4 @@
|
|||||||
|
services:
|
||||||
|
autowire_array:
|
||||||
|
class: Foo
|
||||||
|
autowire: ['set*', 'bar']
|
@ -554,6 +554,20 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
|||||||
$loader->load('services23.xml');
|
$loader->load('services23.xml');
|
||||||
|
|
||||||
$this->assertTrue($container->getDefinition('bar')->isAutowired());
|
$this->assertTrue($container->getDefinition('bar')->isAutowired());
|
||||||
|
$this->assertEquals(array('__construct'), $container->getDefinition('bar')->getAutowiredMethods());
|
||||||
|
|
||||||
|
$loader->load('services27.xml');
|
||||||
|
$this->assertEquals(array('set*', 'bar'), $container->getDefinition('autowire_array')->getAutowiredMethods());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testAutowireAttributeAndTag()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
|
||||||
|
$loader->load('services28.xml');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -323,6 +323,10 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
|
|||||||
$loader->load('services23.yml');
|
$loader->load('services23.yml');
|
||||||
|
|
||||||
$this->assertTrue($container->getDefinition('bar_service')->isAutowired());
|
$this->assertTrue($container->getDefinition('bar_service')->isAutowired());
|
||||||
|
$this->assertEquals(array('__construct'), $container->getDefinition('bar_service')->getAutowiredMethods());
|
||||||
|
|
||||||
|
$loader->load('services27.yml');
|
||||||
|
$this->assertEquals(array('set*', 'bar'), $container->getDefinition('autowire_array')->getAutowiredMethods());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user