Skip to content

[DI] Getter autowiring #21031

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 2, 2017
Merged

[DI] Getter autowiring #21031

merged 1 commit into from
Feb 2, 2017

Conversation

dunglas
Copy link
Member

@dunglas dunglas commented Dec 23, 2016

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:

# app/config/config.yml

services:
    Foo\Bar:
        autowire: ['get*']
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.

Copy link
Contributor

@theofidry theofidry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't help to find it extremely dirty and tricky, as you're really starting to have any method/typehint registering services left and right.

Besides my comments, there is two things I would like to ask/address:

  • How does it works when auto-wiring is not explicit? For example when the typehint is an interface? When the return typehint is the class Bar but an existing service Bar is already registered?
  • My other more serious concern is how easy/hard it is to debug?

I opened #21222 for the second point to avoid hijacking this thread

return false;
}

if (0 !== $reflectionMethod->getNumberOfParameters() || $reflectionMethod->isFinal() || $reflectionMethod->returnsReference() || !($returnType = $reflectionMethod->getReturnType())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the length of the line, IMO $returnType = $reflectionMethod->getReturnType() assignment should be on a separate line

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, we prefer long lines in such cases.

Copy link
Member

@nicolas-grekas nicolas-grekas Jan 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for readability: 0 < $r->getNumberOfParameters()?
for the assignment, move it to a next "if" block?
if (!$returnType = $reflectionMethod->getReturnType()) {

return false;
}

$class = (string) $returnType;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the cast necessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, next methods expect a class name as parameter, not a \ReflectionType instance. An alternative is to call __toString() explicitly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this could be done in the assignment line $returnType = (string) $reflectionMethod->getReturnType()?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, ReflectionType::__toString is deprecated since PHP 7.1, you should use this instead:
on https://github.com/symfony/symfony/blob/master/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php#L1786

if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
@trigger_error(sprintf('The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name), E_USER_DEPRECATED);
}
if ($definition->getOverriddenGetters()) {
$salt = str_replace('.', '', uniqid('', true));
$service = eval(sprintf('return new class(...$arguments) extends %s { private $container%3$s; private $values%3$s; %s };', $r->name, $this->addOverriddenGetters($id, $definition, $r, $salt), $salt));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be in the PhpDumper or something?

// should not be called
}

public function &getReference(): A
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oo, I'm seeing things I should not see :P

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing, i've just never seen a function declared with a reference like that

{
// should not be called
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one extra line

@@ -116,6 +118,8 @@ public function dump(array $options = array())
'debug' => true,
), $options);

$this->containerRef = 'container'.substr(strtr(base64_encode(md5($options['namespace'].'\\'.$options['class'].'+'.$options['base_class'], true)), '+/', '__'), 0, -2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is all of that necessary for the container ref?

@dunglas dunglas force-pushed the getter_autowiring branch 2 times, most recently from 9ed1810 to 3efd930 Compare January 10, 2017 10:37
Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 once #20973 is merged (with minor comments)


namespace Symfony\Component\DependencyInjection\Tests\Fixtures;

if (PHP_VERSION_ID >= 70100) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this check can be removed:, because it doesn't work anyway: the following will trigger a parse error

@@ -497,6 +499,32 @@ public function testExplicitMethodInjection()
);
}

public function testGetterOverriding()
{
if (PHP_VERSION_ID < 70100) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be moved to an @requires PHP 7.1 annotation

{
foreach ($autowiredMethod as $reflectionMethod) {
if (
isset($overridenGetters[$reflectionMethod->name]) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strtolower($reflectionMethod->name)

$this->populateAvailableTypes();
}

$class = $returnType->__toString();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$returnType instanceof \ReflectionNamedType ? $returnType->getName() : $returnType->__toString()
because __toString is deprecated since php 7.1

@dunglas dunglas force-pushed the getter_autowiring branch 2 times, most recently from 088daea to f5b09f4 Compare January 31, 2017 09:52
Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

*
* @return array
*/
private function autowireOverridenGetters(array $overridenGetters, array $autowiredMethod)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$autowiredMethods

|| 0 !== $reflectionMethod->getNumberOfParameters()
|| $reflectionMethod->isFinal()
|| $reflectionMethod->returnsReference()
|| !($returnType = $reflectionMethod->getReturnType())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the whole condition deserves some comments about why autowiring is skipped here. Maybe it's even good to split it in several if expressions.

Copy link
Member

@nicolas-grekas nicolas-grekas Feb 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can partially answer here: this list is just the list of cases where explicit getter injection throws an exception already - thus cases where autowiring should just skip.
A comment would be nice for sure, but splitting the condition is not needed imho.

Copy link
Member

@stof stof left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except for the changed needed by the deprecation of autowiring types, this PR looks good to me

continue;
}

if (null === $this->types) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fabpot
Copy link
Member

fabpot commented Feb 2, 2017

Can you add a note in the CHANGELOG and mark this as being experimental?

@fabpot
Copy link
Member

fabpot commented Feb 2, 2017

Thank you @dunglas.

@fabpot fabpot merged commit c48c36b into symfony:master Feb 2, 2017
fabpot added a commit that referenced this pull request Feb 2, 2017
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
@dunglas dunglas deleted the getter_autowiring branch February 13, 2017 09:23
@fabpot fabpot mentioned this pull request May 1, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants