Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[DependencyInjection] Add Parameter attribute to bind container parameters to services. #44780

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

Closed
wants to merge 1 commit into from
Closed

[DependencyInjection] Add Parameter attribute to bind container parameters to services. #44780

wants to merge 1 commit into from

Conversation

maldoinc
Copy link
Contributor

Q A
Branch? 6.1
Bug fix? no
New feature? yes
Deprecations? no
Tickets
License MIT
Doc PR not yet created. Will update if PR looks to be merged

Hi all.

This PR proposes the addition of a new Attribute to bind container parameters to services.

Motivation:
Seeing as Symfony has been adopting the usage of attributes to configure services, this would add the possibility to bind parameters and have services be self-contained instead of having their configuration split into multiple files.

In the application we develop most services which require parameters have their values declared in a Yaml file via the following syntax.

Acme\Service\DummyService:
    arguments:
        $isDebugEnv: "%kernel.debug%"

Downsides

  • Configuration remains in a separate file which also grows in size with the number of services.
  • Duplication of class,namespace and arguments
  • Error-prone refactoring: namespace, class name, arguments in the yaml file are not updated by any IDE or plugin when they change.

Recently I also became aware of the #[Autoconfigure(bind:['$isDebugEnv' => '%kernel.debug%'])] syntax. This addresses the separation of the declaration into multiple files and follows the class when moved around. If this PR won't pass then this would be our preferred method once the application I help develop is upgraded to PHP 8.

  • The argument name is passed as a string to bind and is also not refactored and is also duplicated.
  • Can only be applied to a class rather than the constructor, having argument names and the constructor be visually "far" from each-other.

Proposal:

The Parameter (or BindParameter, name tbd) attribute which is applied to parameters and properties (to account for promoted properties) and has the parameter name bound via a compiler pass. Currently only parameters are supported so the following is not a valid syntax #[Parameter("%kernel.logs_dir%/subdir")] (also tbd).

This would address the concerns from both previous approaches and in my opinion has a main advantage: It does not rely on strings for the argument name. The php community has been moving away for quite some time now from hard-coded strings to reference things. Classes now have the ::class syntax, and references to functions, properties and methods went from strings and arrays to having first-class support with the new (...) syntax.

The downside of this new attribute would be that it is yet another way to do something that is already possible. On the other hand, Symfony has always had multiple options to do configuration (annotations and now attributes, yaml, xml, php).

Example:

class DummyService
{
    public function __construct(#[Parameter("kernel.debug")] public bool $isDebugEnv)
    {
    }
}

Looking forward to your thoughts. Related issue: #40327

@maldoinc maldoinc requested a review from dunglas as a code owner December 23, 2021 19:34
@carsonbot carsonbot added this to the 6.1 milestone Dec 23, 2021
@carsonbot carsonbot changed the title Add Parameter attribute to bind container parameters to services. [DependencyInjection] Add Parameter attribute to bind container parameters to services. Dec 23, 2021
@carsonbot
Copy link

Hey!

I think @fractalzombie has recently worked with this code. Maybe they can help review this?

Cheers!

Carsonbot

@kbond
Copy link
Member

kbond commented Jan 13, 2022

I like this idea but what about binding services as well? I'm thinking just a single Bind attribute:

class DummyService
{
    public function __construct(
        #[Bind('@another.service.id')]
        public AnotherService $service,
        #[Bind('%kernel.debug%')]
        public bool $isDebugEnv
    ) {
    }
}

This would be a shortcut to the following (which is possible now):

#[Autoconfigure(bind: [
    '$service' => '@another.service.id',
    '$isDebugEnv' => '%kernel.debug%',
])]
class DummyService
{
    public function __construct(public AnotherService $service, public bool $isDebugEnv)
    {
    }
}

@stof
Copy link
Member

stof commented Jan 13, 2022

I like this idea but what about binding services as well?

Isn't it already solved in some way with the Target attribute (which relies on named autowiring aliases rather than on service ids) ?

@kbond
Copy link
Member

kbond commented Jan 13, 2022

Isn't it already solved in some way with the Target attribute (which relies on named autowiring aliases rather than on service ids) ?

Partially, but not for service ids themselves and this was what I was looking for. I'm envisioning this purely as a short-hand for Autoconfigure(bind).

@maldoinc
Copy link
Contributor Author

maldoinc commented Jan 15, 2022

The reason I did not consider it for services is that services are already automatically injected on top of already having the Required attribute docblock annotation and the {with,set}Something methods as well. For services which have ids rather than the FCCN I figured one of the existing solutions could be used instead.

The other thing is that this accepts only parameters rather than a string with a parameter in it such as %param%/some_string. For this, I figured since this is right next to the constructor, the concatenation could be done there. The other reason being that #[Parameter('kernel.debug') looks "cleaner" than #[Parameter("%kernel.debug%")], but something like #[Bind("%kernel.debug%"]) also has its merits. Both syntaxes are fine by me so we can go with whichever is more suitable to the project.

@kbond
Copy link
Member

kbond commented Jan 20, 2022

The reason I did not consider it for services is that services are already automatically injected on top of already having the Required attribute docblock annotation and the {with,set}Something methods as well. For services which have ids rather than the FCCN I figured one of the existing solutions could be used instead.

Yes, this would just be a shortcut for Autoconfigure(bind: ...). There is no other way to configure service id's (that I'm aware).

Recently I also became aware of the #[Autoconfigure(bind:['$isDebugEnv' => '%kernel.debug%'])] syntax. This addresses the separation of the declaration into multiple files and follows the class when moved around. If this PR won't pass then this would be our preferred method once the application I help develop is upgraded to PHP 8.

  • The argument name is passed as a string to bind and is also not refactored and is also duplicated.
  • Can only be applied to a class rather than the constructor, having argument names and the constructor be visually "far" from each-other.

To me, this argument applies to binding services as well as parameters.

parameters rather than a string with a parameter in it such as %param%/some_string

Since Autoconfigure(bind: ...) supports this, I believe the parameter attribute should as well.

@stof
Copy link
Member

stof commented Jan 20, 2022

Based on the various comments here, I think there is a misunderstand about what Autoconfigure(bind: ...) is doing.

the Autoconfigure attribute is not about replacing file-level default bindings. It is about registering a type into the autoconfiguration system (see ContainerBundler::registerForAutoconfiguration) so that all services being of that type will be autoconfigured with that rule (if they have opted-in for autoconfiguration)

@kbond
Copy link
Member

kbond commented Jan 20, 2022

So to clarify, for my second code block here. This would register the $service binding globally (not just in this file)?

Looking at #39804.

This should typically be added on a base class/interface to tell how implementations of such a base type should be autoconfigured.

Indeed, this was a misunderstanding on my part.

So what I'm looking for and I believe @maldoinc is looking for (for parameters at least) is a way to take this config:

services:
    App\SomeService:
        - '@some.other.service'
        - '%some.parameter%'

And use constructor parameter attributes to configure:

class SomeService
{
    public function __construct(
        #[???('some.other.service')]
        public AnotherService $service,
        #[???('some.parameter')]
        public string $parameter
    ) {
    }
}

@stof
Copy link
Member

stof commented Jan 20, 2022

@kbond the Autoconfigure attribute would register a binding applied on all (autoconfigured) service whose class is a DummyService or a child class of DummyService

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 would prefer requiring the % around the parameter name, as this gives more flexibility, eg:
#[Parameter('%kernel.project_dir%/var/foo')]

@@ -38,6 +38,27 @@
*/
class AutowirePassTest extends TestCase
{
/**
* @requires PHP 8.0
Copy link
Member

Choose a reason for hiding this comment

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

not needed anymore


class AutowiredParameterTest
{
public function __construct(#[Parameter("parameter.test")] public string $testParam)
Copy link
Member

Choose a reason for hiding this comment

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

please use single-quoted strings

@nicolas-grekas
Copy link
Member

Thank you @maldoinc for this PR. I'm closing it because we figured out another attribute would be more generic. See #45657, you might like it.

fabpot added a commit that referenced this pull request Mar 18, 2022
…te (kbond)

This PR was merged into the 6.1 branch.

Discussion
----------

[DependencyInjection] add `Autowire` parameter attribute

| Q             | A
| ------------- | ---
| Branch?       | 6.1
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | n/a
| License       | MIT
| Doc PR        | todo

Replaces #45573 & #44780 with a single new `Autowire` attribute:

```php
class MyService
{
    public function __construct(
        #[Autowire(service: 'some_service')]
        private $service1,

        #[Autowire(expression: 'service("App\\Mail\\MailerConfiguration").getMailerMethod()')
        private $service2,

        #[Autowire(value: '%env(json:file:resolve:AUTH_FILE)%')]
        private $parameter1,

        #[Autowire(value: '%kernel.project_dir%/config/dir')]
        private $parameter2,
    ) {}
}
```

Works with controller arguments as well:

```php
class MyController
{
    public function someAction(
        #[Autowire(service: 'some_service')]
        $service1,

        #[Autowire(expression: 'service("App\\Mail\\MailerConfiguration").getMailerMethod()')
        $service2,

        #[Autowire(value: '%env(json:file:resolve:AUTH_FILE)%')]
        $parameter1,

        #[Autowire(value: '%kernel.project_dir%/config/dir')]
        $parameter2,
    ): Response {}
}
```

Commits
-------

d43fe42 [DependencyInjection] add `Autowire` parameter attribute
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.

6 participants