Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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
-------

c48c36b [DI] Add support for getter autowiring
  • Loading branch information
fabpot committed Feb 2, 2017
2 parents 03b7cf7 + c48c36b commit b50efa5
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 9 deletions.
1 change: 1 addition & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Expand Up @@ -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
Expand Down
Expand Up @@ -36,7 +36,7 @@ public function process(ContainerBuilder $container)
try {
parent::process($container);
} finally {
// Free memory and remove circular reference to container
// Free memory
$this->definedTypes = array();
$this->types = null;
$this->ambiguousServiceTypes = array();
Expand Down Expand Up @@ -90,6 +90,7 @@ protected function processValue($value, $isRoot = false)
}

$methodCalls = $this->autowireMethodCalls($reflectionClass, $methodCalls, $autowiredMethods);
$overriddenGetters = $this->autowireOverridenGetters($value->getOverriddenGetters(), $autowiredMethods);

if ($constructor) {
list(, $arguments) = array_shift($methodCalls);
Expand All @@ -103,6 +104,10 @@ protected function processValue($value, $isRoot = false)
$value->setMethodCalls($methodCalls);
}

if ($overriddenGetters !== $value->getOverriddenGetters()) {
$value->setOverriddenGetters($overriddenGetters);
}

return parent::processValue($value, $isRoot);
}

Expand All @@ -124,7 +129,7 @@ private function getMethodsToAutowire(\ReflectionClass $reflectionClass, array $
$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;
}
Expand Down Expand Up @@ -164,7 +169,7 @@ private function autowireMethodCalls(\ReflectionClass $reflectionClass, array $m
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 {
Expand All @@ -177,15 +182,15 @@ private function autowireMethodCalls(\ReflectionClass $reflectionClass, array $m
}
}

$arguments = $this->autowireMethod($reflectionMethod, $arguments, true);
$arguments = $this->autowireMethodCall($reflectionMethod, $arguments, true);

if ($arguments !== $call[1]) {
$methodCalls[$i][1] = $arguments;
}
}

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);
}
}
Expand All @@ -194,7 +199,7 @@ private function autowireMethodCalls(\ReflectionClass $reflectionClass, array $m
}

/**
* Autowires the constructor or a setter.
* Autowires the constructor or a method.
*
* @param \ReflectionMethod $reflectionMethod
* @param array $arguments
Expand All @@ -204,7 +209,7 @@ private function autowireMethodCalls(\ReflectionClass $reflectionClass, array $m
*
* @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) {
Expand Down Expand Up @@ -298,6 +303,55 @@ private function autowireMethod(\ReflectionMethod $reflectionMethod, array $argu
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.
*/
Expand Down
Expand Up @@ -15,6 +15,7 @@
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>
Expand Down Expand Up @@ -516,6 +517,31 @@ public function testExplicitMethodInjection()
);
}

/**
* @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
Expand Down Expand Up @@ -854,6 +880,11 @@ public function notASetter(A $a)
{
// should be called only when explicitly specified
}

protected function setProtectedMethod(A $a)
{
// should not be called
}
}

class SetterInjectionCollision
Expand Down
Expand Up @@ -354,8 +354,8 @@ public function testDumpOverridenGettersWithConstructor()

$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');
Expand Down
@@ -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
}
}

0 comments on commit b50efa5

Please sign in to comment.