Skip to content
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

[DependencyInjection] Anonymous services in PHP DSL #24632

Merged

Conversation

unkind
Copy link
Contributor

@unkind unkind commented Oct 19, 2017

Q A
Branch? 4.1
Bug fix? no
New feature? enhancement for fluent PHP configs
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #24493
License MIT
Doc PR not yet

See #24493:

$s->anonymous(SendEmailForRegisteredUser::class)->tag('listener');

@unkind
Copy link
Contributor Author

unkind commented Oct 19, 2017

I didn't fix all the tests yet (is there any script to update all autogenerated DI-fixtures?), need feedback. ping @nicolas-grekas

UPD: target branch is incorrect. Anyway, any chance to merge it in 3.4? If so, I'll reopen PR.

@unkind unkind force-pushed the enhancement-anonymous-services-fluent-php branch 7 times, most recently from 55997aa to 02bf763 Compare October 19, 2017 18:08
@nicolas-grekas nicolas-grekas added this to the 3.4 milestone Oct 19, 2017
@chalasr chalasr changed the base branch from master to 3.4 October 19, 2017 19:26
@unkind unkind changed the title [DependencyInjection] Anonymous services & decorates() [DependencyInjection] Anonymous services & decorators Oct 19, 2017
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.

I think I'm 👎: this will introduce a non trivial amount of complexity (it's broken already), when the current way just works (just give that decorator a name.)

@@ -22,6 +30,25 @@
*/
final public function args(array $arguments)
{
$arguments = array_map(
Copy link
Member

Choose a reason for hiding this comment

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

Ihe inner can be injected anywhere, not only in the constructor, and can be nested also. The logic should be in AbstractConfigurator::processValue() (but it's not trivial to do so).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

and can be nested also

What do you mean?

The logic should be in AbstractConfigurator::processValue() (but it's not trivial to do so)

It is still in dev branch, later will be much harder to fix it. Probably, it's not the only case when we need Definition object there.

Copy link
Member

Choose a reason for hiding this comment

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

the inner service doesn't have to be injected as an argument: it can be nested in an array, which itself is passed as argument (constructor, or setter, or property in fact.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the inner service doesn't have to be injected as an argument: it can be nested in an array, which itself is passed as argument (constructor, or setter, or property in fact.)

It just requires to tweak AbstractConfigurator::processValue() to make it work, doesn't it?

Copy link
Member

Choose a reason for hiding this comment

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

it should (but I'm still not sure it's worth the added complexity)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It there any reason why it is static? As I can see, AbstractConfigurator contains link to the definition. I'm not big fan of it, but it's cheap solution.

Copy link
Member

Choose a reason for hiding this comment

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

it's used in function iterator(array $values)

);
}

return new ReferenceConfigurator($decorated[1] ? $decorated[1] : $this->id . '.inner');
Copy link
Member

Choose a reason for hiding this comment

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

".inner" is not hardcoded when decorating services, see "renamedId".

Copy link
Contributor Author

@unkind unkind Oct 19, 2017

Choose a reason for hiding this comment

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

I don't get it. $decorated[1] contains value of renamedId. I use .inner suffix when user didn't specify renamedId.

Copy link
Member

Choose a reason for hiding this comment

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

oh ok!

@unkind
Copy link
Contributor Author

unkind commented Oct 19, 2017

I think I'm 👎: this will introduce a non trivial amount of complexity (it's broken already), when the current way just works (just give that decorator a name.)

What about just anonymous services without decorators?

@nicolas-grekas
Copy link
Member

What about just anonymous services without decorators?

I would do it without the "anonymous" helper: ->set(null, Fqcn::class)

@unkind
Copy link
Contributor Author

unkind commented Oct 19, 2017

I would do it without the "anonymous" helper

Well, a lot (probably, most) of my services are anonymous: listeners, command handlers, API controllers, console commands, cache/monitoring/logging decorators. I can use set(SendEmailForRegisteredUserListener::class, ...) for most of them, but they include namespace and service name becomes very long. On other hand, typing set(null, ...) again and again doesn't make me happy as well.

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Oct 19, 2017

My fear is that ppl will get confused when reading
->set(Foo::class)
vs
->anonymous(Foo::class)
the difference is not obvious if you're not used to anonymous services (how many of us are, of many new comers...)
->set(null, Foo::class) on the contrary is "more" different than ->set(Foo::class)

And I'm also fearing that writing actually might become more confusing: "anonymous" would become a new item on the autocompletion list, which ppl might have a hard time figure out what it means (because it's not that common) - and even scarier: ppl doing the mistake of selecting the wrong set/anonymous choice might have an even harder time figuring out their mistake.

@unkind
Copy link
Contributor Author

unkind commented Oct 19, 2017

the difference is not obvious if you're not used to anonymous services
And I'm also fearing that writing actually might become more confusing

Let them learn the difference. They won't hurt themselves, it's not a gun.

(how many of us are, of many new comers...)

I think most of us have such services in our codebases, we probably just don't realize it. It's yet another reason to make it explicit in my opinion.

@unkind unkind force-pushed the enhancement-anonymous-services-fluent-php branch 3 times, most recently from f304759 to 766ceb3 Compare October 20, 2017 20:46
@unkind unkind changed the title [DependencyInjection] Anonymous services & decorators [DependencyInjection] Anonymous services Oct 20, 2017
@unkind
Copy link
Contributor Author

unkind commented Oct 20, 2017

I've removed decorators, tests are OK except some unrelated ones.

Let's focus on the anonymous() helper. Do you really want to remove it? I'd like to see it as explicit way to register a service, I really don't like nullables, it's almost always better to introduce new straightforward method. I also really doubt it's sort of "forbidden knowledge" that may bring any problems to people. They'd rather discover something new for themselves.

@unkind
Copy link
Contributor Author

unkind commented Oct 20, 2017

Offtopic:

just give that decorator a name

This annoys in the same way as requirement to specify name for "anonymous" services: I don't need it. Considering we have name convention for service names like game.infrastructure.players_qs_memcache_decorator.inner, it becomes a problem. We also have sometimes 3-4 similar services with decorators, so I copy-paste definitions and trying to find the difference in names which I don't even need, ugh.

@nicolas-grekas nicolas-grekas modified the milestones: 3.4, 4.1 Oct 21, 2017
@nicolas-grekas
Copy link
Member

I'm still really unsold on the topic, if anything, ->set(null, Foo::class) should be the way to IMHO.
Services don't have to be anonymous. Giving them a name is fine (esp. when the name is automatically set as is now.)

@unkind
Copy link
Contributor Author

unkind commented Nov 26, 2017

@nicolas-grekas it looks like a straw man for me: I didn't say that services have to be anonymous. Just many of them could be. Giving name is fine, but sometimes is totally pointless like rituals.

->set(null, Foo::class)

Giving a name for this construction is fine, isn't it? ->set(null, Foo::class) is WTF syntax and it's harder to google it.

The whole discussion is about explicit vs implicit syntax for the same concept. I don't understand your point why this kind of syntax should be implicit.

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Nov 26, 2017

Oh! :)
could be would need a reason. When doing ->set(Foo::class) is actually fine, I really don't see benefit of doing ->anonymous(Foo::class). The first providing a name is a side effect that benefits everyone when needing a name, and doesn't hurt anyone when a name is actually not needed. That's my reasoning. Just let SF give a name for you, and ignore it if you don't need it.
The only case where the name is actually boilerplate if when you need several services implemented by the same class. Which is a case where you have to type some configuration anyway, and doing ->set(null, Foo::class) would just work and not look like impossible boilerplate IMHO.

@unkind
Copy link
Contributor Author

unkind commented Dec 18, 2017

It should be quite easy to define your own DSL, by creating a different set of configurator classes, and type-hinting for them in the closure.

What do you think of decorators for ContainerConfigurator?

@@ -23,6 +25,16 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura
  */
 class PhpFileLoader extends FileLoader
 {
+    private $configuratorDecorator;
+
+    public static function withConfiguratorDecorator(ContainerBuilder $container, FileLocatorInterface $locator, callable $configuratorDecorator): PhpFileLoader
+    {
+        $loader = new self($container, $locator);
+        $loader->configuratorDecorator = $configuratorDecorator;
+
+        return $loader;
+    }
+
     /**
      * {@inheritdoc}
      */
@@ -44,7 +56,13 @@ class PhpFileLoader extends FileLoader
         $callback = $load($path);

         if ($callback instanceof \Closure) {
-            $callback(new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource), $this->container, $this);
+            $configurator = new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource);
+
+            if ($this->configuratorDecorator) {
+                $configurator = call_user_func($this->configuratorDecorator, $configurator);
+            }
+
+            $callback($configurator, $this->container, $this);
         }

@ro0NL
Copy link
Contributor

ro0NL commented Dec 28, 2017

set(null, Foo::class) doesnt work in 4.0 due string $id type :) which is also the phpdoc in 3.4, this is actually consistent with the current loaders right.

Anyway, just walked into this trying to register a event listener twice. Now in a codebase full of set(Fqcn::class) the set(null, Fqcn::class) (if it worked) looks a bit odd... but im really thinking of a good ID value now 🤔 (the "naming is hard" issue also counts a bit IMHO).

@unkind
Copy link
Contributor Author

unkind commented Dec 28, 2017

naming is hard

Especially when you don't need a name (anonymous/lambda functions is a very close example).

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Dec 29, 2017

set(null, Foo::class) doesnt work in 4.0 due string $id type

which is not an issue since the method is final, so can be changed without any BC break

@ro0NL
Copy link
Contributor

ro0NL commented Dec 29, 2017

Hm looking at failing tests locally

+    :\n
+        class: App\BarService\n
+        public: true\n

that's bad right? :)

@unkind
Copy link
Contributor Author

unkind commented Dec 29, 2017

@ro0NL if you mean my branch, it's possible if you merged with 3.4/master because ContainerBuilderTest was changed (GitHub shows the conflict). I could resolve it, but I don't understand the state of PR.

Do I understand correctly that in order to merge it I have to replace ->anonymous() with ->set(null, ...)?

@ro0NL
Copy link
Contributor

ro0NL commented Dec 29, 2017

right. I tested on 3.4 :) i thought it worked today already. If we can settle with set(null, Fqcn::class) im fine 👍

@unkind
Copy link
Contributor Author

unkind commented Jan 4, 2018

@nicolas-grekas can you change base branch on master?

@curry684
Copy link
Contributor

curry684 commented Jan 4, 2018

You can do it yourself with the edit button at the top of the issue.

@unkind unkind changed the base branch from 3.4 to master January 4, 2018 01:31
@unkind
Copy link
Contributor Author

unkind commented Jan 4, 2018

@curry684 thanks, didn't know it.

@nicolas-grekas nicolas-grekas force-pushed the enhancement-anonymous-services-fluent-php branch from d709412 to 8b6945d Compare January 22, 2018 19:30
@nicolas-grekas nicolas-grekas changed the title [DependencyInjection] Anonymous services [DependencyInjection] Anonymous services in PHP DSL Jan 22, 2018
@nicolas-grekas nicolas-grekas force-pushed the enhancement-anonymous-services-fluent-php branch from 8b6945d to 2348c11 Compare January 22, 2018 19:32
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.

(@unlinkd FYI, I pushed some changes on your fork, was quicker for me this way. Ready on my side thank you.)

@unkind
Copy link
Contributor Author

unkind commented Jan 22, 2018

OK, looks good.

@fabpot
Copy link
Member

fabpot commented Jan 23, 2018

Cannot be merged as tests are broken.

@nicolas-grekas nicolas-grekas force-pushed the enhancement-anonymous-services-fluent-php branch from 2348c11 to ee42276 Compare January 23, 2018 06:58
@nicolas-grekas
Copy link
Member

Tests fixed, should be green in a few minutes.

@fabpot
Copy link
Member

fabpot commented Jan 23, 2018

Thank you @unkind.

@fabpot fabpot merged commit ee42276 into symfony:master Jan 23, 2018
fabpot added a commit that referenced this pull request Jan 23, 2018
…nkind)

This PR was merged into the 4.1-dev branch.

Discussion
----------

[DependencyInjection] Anonymous services in PHP DSL

| Q             | A
| ------------- | ---
| Branch?       | 4.1
| Bug fix?      | no
| New feature?  | enhancement for fluent PHP configs
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #24493
| License       | MIT
| Doc PR        | not yet

See #24493:
```php
$s->anonymous(SendEmailForRegisteredUser::class)->tag('listener');
```

Commits
-------

ee42276 [DI] Add way to register service with implicit name using the PHP DSL
@ro0NL
Copy link
Contributor

ro0NL commented Jan 23, 2018

Nice one @unkind. Thx super useful :)

@fabpot fabpot mentioned this pull request May 7, 2018
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.

None yet

10 participants