feature #21031 [DI] Getter autowiring (dunglas)
This PR was merged into the 3.3-dev branch.
Discussion
----------
[DI] Getter autowiring
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | n/a
| License | MIT
| Doc PR | todo
This PR adds support for getter autowiring. #20973 must be merged first.
Example:
```yaml
# app/config/config.yml
services:
Foo\Bar:
autowire: ['get*']
```
```php
namespace Foo;
class Bar
{
protected function getBaz(): Baz // this feature only works with PHP 7+
{
}
}
class Baz
{
}
````
`Baz` will be automatically registered as a service and an instance will be returned when `Bar::getBaz` will be called (and only at this time, lazy loading).
This feature requires PHP 7 or superior.
Commits
-------
c48c36be8f
[DI] Add support for getter autowiring
This commit is contained in:
commit
b50efa5006
@ -8,6 +8,7 @@ CHANGELOG
|
||||
* deprecated `ContainerBuilder::getClassResource()`, use `ContainerBuilder::getReflectionClass()` or `ContainerBuilder::addObjectResource()` instead
|
||||
* added `ContainerBuilder::fileExists()` for checking and tracking file or directory existence
|
||||
* deprecated autowiring-types, use aliases instead
|
||||
* [EXPERIMENTAL] added support for getter autowiring
|
||||
* [EXPERIMENTAL] added support for getter-injection
|
||||
* added support for omitting the factory class name in a service definition if the definition class is set
|
||||
* deprecated case insensitivity of service identifiers
|
||||
|
@ -36,7 +36,7 @@ class AutowirePass extends AbstractRecursivePass
|
||||
try {
|
||||
parent::process($container);
|
||||
} finally {
|
||||
// Free memory and remove circular reference to container
|
||||
// Free memory
|
||||
$this->definedTypes = array();
|
||||
$this->types = null;
|
||||
$this->ambiguousServiceTypes = array();
|
||||
@ -90,6 +90,7 @@ class AutowirePass extends AbstractRecursivePass
|
||||
}
|
||||
|
||||
$methodCalls = $this->autowireMethodCalls($reflectionClass, $methodCalls, $autowiredMethods);
|
||||
$overriddenGetters = $this->autowireOverridenGetters($value->getOverriddenGetters(), $autowiredMethods);
|
||||
|
||||
if ($constructor) {
|
||||
list(, $arguments) = array_shift($methodCalls);
|
||||
@ -103,6 +104,10 @@ class AutowirePass extends AbstractRecursivePass
|
||||
$value->setMethodCalls($methodCalls);
|
||||
}
|
||||
|
||||
if ($overriddenGetters !== $value->getOverriddenGetters()) {
|
||||
$value->setOverriddenGetters($overriddenGetters);
|
||||
}
|
||||
|
||||
return parent::processValue($value, $isRoot);
|
||||
}
|
||||
|
||||
@ -124,7 +129,7 @@ class AutowirePass extends AbstractRecursivePass
|
||||
$regexList[] = '/^'.str_replace('\*', '.*', preg_quote($pattern, '/')).'$/i';
|
||||
}
|
||||
|
||||
foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
|
||||
foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $reflectionMethod) {
|
||||
if ($reflectionMethod->isStatic()) {
|
||||
continue;
|
||||
}
|
||||
@ -164,7 +169,7 @@ class AutowirePass extends AbstractRecursivePass
|
||||
list($method, $arguments) = $call;
|
||||
$method = $parameterBag->resolveValue($method);
|
||||
|
||||
if (isset($autowiredMethods[$lcMethod = strtolower($method)])) {
|
||||
if (isset($autowiredMethods[$lcMethod = strtolower($method)]) && $autowiredMethods[$lcMethod]->isPublic()) {
|
||||
$reflectionMethod = $autowiredMethods[$lcMethod];
|
||||
unset($autowiredMethods[$lcMethod]);
|
||||
} else {
|
||||
@ -177,7 +182,7 @@ class AutowirePass extends AbstractRecursivePass
|
||||
}
|
||||
}
|
||||
|
||||
$arguments = $this->autowireMethod($reflectionMethod, $arguments, true);
|
||||
$arguments = $this->autowireMethodCall($reflectionMethod, $arguments, true);
|
||||
|
||||
if ($arguments !== $call[1]) {
|
||||
$methodCalls[$i][1] = $arguments;
|
||||
@ -185,7 +190,7 @@ class AutowirePass extends AbstractRecursivePass
|
||||
}
|
||||
|
||||
foreach ($autowiredMethods as $reflectionMethod) {
|
||||
if ($arguments = $this->autowireMethod($reflectionMethod, array(), false)) {
|
||||
if ($reflectionMethod->isPublic() && $arguments = $this->autowireMethodCall($reflectionMethod, array(), false)) {
|
||||
$methodCalls[] = array($reflectionMethod->name, $arguments);
|
||||
}
|
||||
}
|
||||
@ -194,7 +199,7 @@ class AutowirePass extends AbstractRecursivePass
|
||||
}
|
||||
|
||||
/**
|
||||
* Autowires the constructor or a setter.
|
||||
* Autowires the constructor or a method.
|
||||
*
|
||||
* @param \ReflectionMethod $reflectionMethod
|
||||
* @param array $arguments
|
||||
@ -204,7 +209,7 @@ class AutowirePass extends AbstractRecursivePass
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function autowireMethod(\ReflectionMethod $reflectionMethod, array $arguments, $mustAutowire)
|
||||
private function autowireMethodCall(\ReflectionMethod $reflectionMethod, array $arguments, $mustAutowire)
|
||||
{
|
||||
$didAutowire = false; // Whether any arguments have been autowired or not
|
||||
foreach ($reflectionMethod->getParameters() as $index => $parameter) {
|
||||
@ -298,6 +303,55 @@ class AutowirePass extends AbstractRecursivePass
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Autowires getters.
|
||||
*
|
||||
* @param array $overridenGetters
|
||||
* @param array $autowiredMethods
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function autowireOverridenGetters(array $overridenGetters, array $autowiredMethods)
|
||||
{
|
||||
foreach ($autowiredMethods as $reflectionMethod) {
|
||||
if (isset($overridenGetters[strtolower($reflectionMethod->name)])
|
||||
|| !method_exists($reflectionMethod, 'getReturnType')
|
||||
|| 0 !== $reflectionMethod->getNumberOfParameters()
|
||||
|| $reflectionMethod->isFinal()
|
||||
|| $reflectionMethod->returnsReference()
|
||||
|| !$returnType = $reflectionMethod->getReturnType()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$typeName = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : $returnType->__toString();
|
||||
|
||||
if ($this->container->has($typeName) && !$this->container->findDefinition($typeName)->isAbstract()) {
|
||||
$overridenGetters[$reflectionMethod->name] = new Reference($typeName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $this->types) {
|
||||
$this->populateAvailableTypes();
|
||||
}
|
||||
|
||||
if (isset($this->types[$typeName])) {
|
||||
$value = new Reference($this->types[$typeName]);
|
||||
} elseif ($returnType = $this->container->getReflectionClass($typeName, true)) {
|
||||
try {
|
||||
$value = $this->createAutowiredDefinition($returnType);
|
||||
} catch (RuntimeException $e) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
$overridenGetters[$reflectionMethod->name] = $value;
|
||||
}
|
||||
|
||||
return $overridenGetters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the list of available types.
|
||||
*/
|
||||
|
@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\GetterOverriding;
|
||||
|
||||
/**
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
@ -516,6 +517,31 @@ class AutowirePassTest extends \PHPUnit_Framework_TestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 7.1
|
||||
*/
|
||||
public function testGetterOverriding()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('b', B::class);
|
||||
|
||||
$container
|
||||
->register('getter_overriding', GetterOverriding::class)
|
||||
->setOverriddenGetter('getExplicitlyDefined', new Reference('b'))
|
||||
->setAutowiredMethods(array('get*'))
|
||||
;
|
||||
|
||||
$pass = new AutowirePass();
|
||||
$pass->process($container);
|
||||
|
||||
$overridenGetters = $container->getDefinition('getter_overriding')->getOverriddenGetters();
|
||||
$this->assertEquals(array(
|
||||
'getexplicitlydefined' => new Reference('b'),
|
||||
'getfoo' => new Reference('autowired.Symfony\Component\DependencyInjection\Tests\Compiler\Foo'),
|
||||
'getbar' => new Reference('autowired.Symfony\Component\DependencyInjection\Tests\Compiler\Bar'),
|
||||
), $overridenGetters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getCreateResourceTests
|
||||
* @group legacy
|
||||
@ -854,6 +880,11 @@ class SetterInjection
|
||||
{
|
||||
// should be called only when explicitly specified
|
||||
}
|
||||
|
||||
protected function setProtectedMethod(A $a)
|
||||
{
|
||||
// should not be called
|
||||
}
|
||||
}
|
||||
|
||||
class SetterInjectionCollision
|
||||
|
@ -354,8 +354,8 @@ class PhpDumperTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$dump = $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Overriden_Getters_With_Constructor'));
|
||||
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_dump_overriden_getters_with_constructor.php', $dump);
|
||||
$resources = array_map('strval', $container->getResources());
|
||||
$this->assertContains(realpath(self::$fixturesPath.'/containers/container_dump_overriden_getters_with_constructor.php'), $resources);
|
||||
$res = $container->getResources();
|
||||
$this->assertSame('reflection.Symfony\Component\DependencyInjection\Tests\Fixtures\Container34\Foo', (string) array_pop($res));
|
||||
|
||||
$baz = $container->get('baz');
|
||||
$r = new \ReflectionMethod($baz, 'getBaz');
|
||||
|
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Tests\Compiler\A;
|
||||
use Symfony\Component\DependencyInjection\Tests\Compiler\B;
|
||||
use Symfony\Component\DependencyInjection\Tests\Compiler\Bar;
|
||||
use Symfony\Component\DependencyInjection\Tests\Compiler\Foo;
|
||||
|
||||
/**
|
||||
* To test getter autowiring with PHP >= 7.1.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class GetterOverriding
|
||||
{
|
||||
public function getFoo(): ?Foo
|
||||
{
|
||||
// should be called
|
||||
}
|
||||
|
||||
protected function getBar(): Bar
|
||||
{
|
||||
// should be called
|
||||
}
|
||||
|
||||
public function getNoTypeHint()
|
||||
{
|
||||
// should not be called
|
||||
}
|
||||
|
||||
public function getUnknown(): NotExist
|
||||
{
|
||||
// should not be called
|
||||
}
|
||||
|
||||
public function getExplicitlyDefined(): B
|
||||
{
|
||||
// should be called but not autowired
|
||||
}
|
||||
|
||||
public function getScalar(): string
|
||||
{
|
||||
// should not be called
|
||||
}
|
||||
|
||||
final public function getFinal(): A
|
||||
{
|
||||
// should not be called
|
||||
}
|
||||
|
||||
public function &getReference(): A
|
||||
{
|
||||
// should not be called
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user