Skip to content

[DependencyInjection] Add support for named arguments #21383

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 3 commits into from
Feb 13, 2017

Conversation

dunglas
Copy link
Member

@dunglas dunglas commented Jan 23, 2017

Q A
Branch? master
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? yes
Tests pass? yes
Fixed tickets -
License MIT
Doc PR todo

This PR introduces named arguments for services definitions. It's especially useful to inject parameters in an autowired service. It is (at least partially) an alternative to #21376 and #20738.

Usage:

services:
    _defaults: { autowire: true }
    Acme\NewsletterManager: { $apiKey: "%mandrill_api_key%" }

# Alternative (traditional) syntax
services:
    newsletter_manager:
        class: Acme\NewsletterManager
        arguments:
            $apiKey: "%mandrill_api_key%"
        autowire: true
use Doctrine\ORM\EntityManager;
use Psr\Log\LoggerInterface;

namespace Acme;

class NewsletterManager
{
    private $logger;
    private $em;
    private $apiKey;

    public function __construct(LoggerInterface $logger, EntityManager $em, $apiKey)
    {
        $this->logger = $logger;
        $this->em = $em;
        $this->apiKey = $apiKey;
    }
}

@nicolas-grekas
Copy link
Member

I like it.
Should be made to work with method calls.
Missing resources tracking for now.
With now two alts to #20738, I think we should close it :)

@fabpot
Copy link
Member

fabpot commented Jan 23, 2017

Note that this conflicts with the implementation of #21313 that was just merged.

@ogizanagi
Copy link
Contributor

ogizanagi commented Jan 23, 2017

Note that this conflicts with the implementation of #21313 that was just merged.

Not really (AFAIU). But can't be used along with.

Side-note: I've already seen some projects using arguments keys, despite it had no effect until now (simply for "increasing readability"). Shouldn't it be considered as a BC break?

@fabpot
Copy link
Member

fabpot commented Jan 23, 2017

services:
    App\Foo\Bar: {'@baz', 'foo', apiKey: "%mandril_api_key%"}

@dunglas
Copy link
Member Author

dunglas commented Jan 23, 2017

@ogizanagi in my opinion no, it was explicitly documented to support only packed arrays (see my changes in Definition).
If it really matters, I can skip named parameters not matching instead of throwing, but it will be harder to debug.

@dunglas
Copy link
Member Author

dunglas commented Jan 23, 2017

Not really (AFAIU). But can't be used along with.

Indeed, I'll try to adapt my PR to be able to use both syntaxes together.

This would be nice but it actually doesn't work:

services:
    Acme\NewsletterManager: { apiKey: "%mandril_api_key%" }

But this works:

services:
    Acme\NewsletterManager:
        arguments: { apiKey: "%mandril_api_key%" }

@dunglas
Copy link
Member Author

dunglas commented Jan 23, 2017

@nicolas-grekas

Should be made to work with method calls.

It would be a nice addition, I propose to do it in another PR when this one will be reviewed and merged.

Missing resources tracking for now.

Done, I rely on @weaverryan's implementation for now because conditions to rebuild the container will be the same if we add calls support later.

With now two alts to #20738, I think we should close it :)

Done!

@dunglas
Copy link
Member Author

dunglas commented Jan 23, 2017

@kasparsklavins we are trying to find the best way to inject parameters when using autowiring and service auto-registration (#21289). It will be a brand new way to use Symfony that will change drastically the first experience for new users and allow to develop faster.
For now, there is no way to do it. Please consider the big picture. Btw, you can already define a service using XML, YAML, PHP, your own loader, autowiring and so on...

@dunglas
Copy link
Member Author

dunglas commented Jan 23, 2017

@fabpot @ogizanagi I've tried something to support the short syntax: b8c0e7c WDYT?

@ogizanagi
Copy link
Contributor

ogizanagi commented Jan 23, 2017

Looks good to me, as soon as we warn in the documentation no argument should be named the same as one of the definition keywords, otherwise it won't work.
I don't know if we can do better for now :)

@ogizanagi
Copy link
Contributor

ogizanagi commented Jan 24, 2017

Re-thinking about it, it also means introducing a new keyword someday means a potential BC break for someone using the short syntax along with named arguments 😕
So maybe this tradeoff isn't worth it and the short syntax should be kept for most simple usages.

@nicolas-grekas
Copy link
Member

Enforce a leading $, eg $apiKey: '%foo%'?
About method calls, is it that a big change ? Because that feels incomplete to me to not have it right now...

@linaori
Copy link
Contributor

linaori commented Jan 24, 2017

One minor issue I see with this: when you rename a variable in php, it will now crash, but this won't be detected with applications (or bundles) with only unit-tests.

@dunglas
Copy link
Member Author

dunglas commented Jan 24, 2017

@iltar you're right but it's already the case for traditional definitions (if you add or remove an argument it will fail) and for calls definitions (if you rename the method it will fail).

It's why unit testing is not enough and apps need to have a pyramid of tests including some functional and UI ones.

@linaori
Copy link
Contributor

linaori commented Jan 24, 2017

@dunglas correct, however, there's a bunch of refactoring tools and find/replaces that might modify the name of a variable. Correctly removing one without causing parse errors is a lot more complex, hence this issue is faster to present itself.

@dunglas
Copy link
Member Author

dunglas commented Jan 24, 2017

PR updated to make named arguments starting with a $ as suggested by @nicolas-grekas. It solves problems raided by @ogizanagi (no conflict possible now) and partially the one solved by @iltar (the $ makes it explicit that there is a special handling, and IDEs can add support for this new convention to allow fluent refactoring).

}

if ($originalArguments !== $arguments) {
$value->setArguments($arguments);
Copy link
Member

Choose a reason for hiding this comment

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

ksort($arguments) before set?

private function isUsingShortSyntax(array $service)
{
foreach ($service as $key => $value) {
if (!is_int($key) && '$' !== substr($key, 0, 1)) {
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 an || instead of &&?
I'd write it as if (!isset($key[0]) || '$' !== $key[0]) {

Copy link
Member Author

Choose a reason for hiding this comment

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

should be an || instead of &&?

No it's correct (only int and keys starting with a $ are allowed when using short syntax)

@@ -245,7 +261,7 @@ private function parseDefinition($id, $service, $file, array $defaults)
return;
}

if (is_array($service) && array_values($service) === $service) {
if (is_array($service) && $this->isUsingShortSyntax($service)) {
Copy link
Member

Choose a reason for hiding this comment

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

note that array_values checked the order of keys, whereas isUsingShortSyntax doesn't anymore, isn't it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed, is it a problem?

Copy link
Member

Choose a reason for hiding this comment

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

no :)

private function isUsingShortSyntax(array $service)
{
foreach ($service as $key => $value) {
if (!is_int($key) && (isset($key[0]) && '$' !== $key[0])) {
Copy link
Member

@nicolas-grekas nicolas-grekas Jan 24, 2017

Choose a reason for hiding this comment

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

the empty string case should also return false, isn't it? thus:
if (is_string($key) && ('' === $key || '$' !== $key[0])) {

Copy link
Member Author

Choose a reason for hiding this comment

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

Why? If it's an empty, string, it's not a short definition.

Copy link
Member

Choose a reason for hiding this comment

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

that's exactly what I'm saying :)

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@nicolas-grekas nicolas-grekas added this to the 3.3 milestone Jan 24, 2017
@dunglas dunglas force-pushed the di/named_arguments branch from 2e5cabb to 0841285 Compare January 24, 2017 12:13
@javiereguiluz
Copy link
Member

I don't like the $ at all. It makes the YAML file look a mix of PHP and YAML contents. Example:

arguments:
    $apiKey: "%mandrill_api_key%"

@zanbaldwin
Copy link
Member

I think the $ is appropriate. It makes it much more obvious that the keys are directly referencing PHP variables/parameters (as defined in the method definition) rather than some non-trivial structure defined by something like the config component.
Making the arguments configuration seem a little less "magic" to newer developers (meaning a lower learning curve) is, to me personally, more important than clean looking configuration files.

@fabpot
Copy link
Member

fabpot commented Feb 13, 2017

@weaverryan Can we close #21376?

@fabpot
Copy link
Member

fabpot commented Feb 13, 2017

@dunglas Looks like another one where a doc PR is needed :)

@fabpot
Copy link
Member

fabpot commented Feb 13, 2017

Thank you @dunglas.

@fabpot fabpot merged commit 8a126c8 into symfony:master Feb 13, 2017
fabpot added a commit that referenced this pull request Feb 13, 2017
…(dunglas, nicolas-grekas)

This PR was merged into the 3.3-dev branch.

Discussion
----------

[DependencyInjection] Add support for named arguments

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | todo

This PR introduces named arguments for services definitions. It's especially useful to inject parameters in an autowired service. It is (at least partially) an alternative to #21376 and #20738.

Usage:

```yml
services:
    _defaults: { autowire: true }
    Acme\NewsletterManager: { $apiKey: "%mandrill_api_key%" }

# Alternative (traditional) syntax
services:
    newsletter_manager:
        class: Acme\NewsletterManager
        arguments:
            $apiKey: "%mandrill_api_key%"
        autowire: true
```
```php
use Doctrine\ORM\EntityManager;
use Psr\Log\LoggerInterface;

namespace Acme;

class NewsletterManager
{
    private $logger;
    private $em;
    private $apiKey;

    public function __construct(LoggerInterface $logger, EntityManager $em, $apiKey)
    {
        $this->logger = $logger;
        $this->em = $em;
        $this->apiKey = $apiKey;
    }
}
```

Commits
-------

8a126c8 [DI] Deprecate string keys in arguments
2ce36a6 [DependencyInjection] Add a new pass to check arguments validity
6e50129 [DependencyInjection] Add support for named arguments
@weaverryan
Copy link
Member

I've at least got a docs issue opened for this! symfony/symfony-docs#7482

... and I closed my other PR - #21376

@teohhanhui
Copy link
Contributor

The $ prefix is unfortunate. PHP-ism seeping into YAML.

@fabpot fabpot mentioned this pull request May 1, 2017
azine pushed a commit to azine/AzineMailgunWebhooksBundle that referenced this pull request May 6, 2017
This breaks on Symfony 3.3.0-Beta1 because of [changes in DI](symfony/symfony#21383 (comment)). Named keys have never been officially supported.
@dunglas dunglas deleted the di/named_arguments branch May 9, 2017 10:03
*
* @throws InvalidArgumentException
*
* @return array
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be hinted as \ReflectionParameter[] so the following calls make more sense

Copy link
Member Author

Choose a reason for hiding this comment

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

This method has been removed during a subsequent refactoring.

@@ -213,12 +213,14 @@ public function getArgument($index)
*/
public function replaceArgument($index, $value)
Copy link
Contributor

Choose a reason for hiding this comment

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

the phpdoc of $index needs to be updated to allow string

Copy link
Contributor

Choose a reason for hiding this comment

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

also ChildDefinition::getArgument doesn't work with string yet. whereas you changed the parent Definition::getArgument.

fabpot added a commit that referenced this pull request Jun 5, 2017
…ition (dunglas)

This PR was squashed before being merged into the 3.3 branch (closes #22981).

Discussion
----------

[DependencyInjection] Fix named args support in ChildDefinition

| Q             | A
| ------------- | ---
| Branch?       | 3.3
| Bug fix?      | yes
| New feature?  | no <!-- don't forget updating src/**/CHANGELOG.md files -->
| BC breaks?    | no
| Deprecations? | no <!-- don't forget updating UPGRADE-*.md files -->
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | n/a

Following @Tobion's review of #21383.

Commits
-------

1ab3e41 [DependencyInjection] Fix named args support in ChildDefinition
@alcohol
Copy link
Contributor

alcohol commented Jul 27, 2017

For reasons unknown to me, my currently client had tons of service definitions that looked like:

service.id:
  class: Foo
  arguments:
    foo: something
    bar: somethingelse
<?php

class Foo {
  public function __construct($foo, $bar) { 
    // $foo would be something
    // $bar would be somethingelse
  }
}

All of those broke with the introduction of these changes in 3.3.

Invalid key "X" found in arguments of method "X" for service "X": only integer or $named arguments are allowed.

Was this intentional? Was their use-case undocumented and thus not supported? Cause it did work exactly as expected before 3.3...

@linaori
Copy link
Contributor

linaori commented Jul 28, 2017

Afaik, the keys were unused and therefore treated by sequence. Writing it down like this wasn't a supported feature either.

@alcohol
Copy link
Contributor

alcohol commented Jul 28, 2017

That's what I figured. They were using the keys themselves though in some cases, by retrieving the arguments of the service definition, in some CompilerPass related stuff. Took me a bit to sort everything out 😅

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.