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] Add #[AutowireInline] attribute to allow service definition at the class level #52820

Merged

Conversation

DaDeather
Copy link
Contributor

@DaDeather DaDeather commented Nov 30, 2023

Q A
Branch 7.1
Bug fix no
New feature yes
Deprecations no
Issues Fix #52819
License MIT

For the idea behind this feature see the issue that contains examples #52819

Example usage:

class SomeSourceAwareLogger
{
    public function __construct(
        private readonly LoggerInterface $logger,
        private readonly string $someSource,
    ) {
    }
}

class SomeSourceAwareLoggerFactory
{
    public function __construct(
        private readonly LoggerInterface $logger,
    ) {
    }

    public function create(string $someSource): SomeSourceAwareLogger
    {
        return new SomeSourceAwareLogger($this->logger, $someSource);
    }

    public static function staticCreate(LoggerInterface $logger, string $someSource): SomeSourceAwareLogger
    {
        return new SomeSourceAwareLogger($logger, $someSource);
    }
}

// -----------

class SomeClass1
{
    public function __construct(
        #[AutowireInline(class: SomeSourceAwareLogger::class, args: [new Reference(LoggerInterface::class), 'bar'])]
        public SomeSourceAwareLogger $someSourceAwareLogger,
    ) {
    }
}

// AND/OR

class SomeClass2
{
    public function __construct(
        #[AutowireInline(
            class: SomeSourceAwareLogger::class,
            factory: [SomeSourceAwareLoggerFactory::class, 'staticCreate'],
            args: [new Reference(LoggerInterface::class), 'someParam'],
        )]
        public SomeSourceAwareLogger $someSourceAwareLogger,
    ) {
    }
}

// AND/OR

class SomeClass3
{
    public function __construct(
        #[AutowireInline(
            class: SomeSourceAwareLogger::class,
            factory: [new Reference(SomeSourceAwareLoggerFactory::class), 'create'],
            args: ['someParam'],
        )]
        public SomeSourceAwareLogger $someSourceAwareLogger,
    ) {
    }
}

@carsonbot carsonbot added this to the 6.4 milestone Nov 30, 2023
@DaDeather DaDeather force-pushed the 52819-dependency-injection-new-inline-attributes branch 2 times, most recently from 6ca710b to c547abc Compare November 30, 2023 11:19
@nicolas-grekas
Copy link
Member

Thanks for proposing and for submitting!

I'm wondering if we couldn't get rid of the interface and rename the proposed argument to e.g. AutowireInline?
To remove the interface, we could maybe make AutowireCallable extend AutowireInline.
Also, instead of a dedicated attribute for services built by factory, can't we add a factory argument to the AutowireInline attribute?

@DaDeather
Copy link
Contributor Author

Thanks for proposing and for submitting!

I'm wondering if we couldn't get rid of the interface and rename the proposed argument to e.g. AutowireInline? To remove the interface, we could maybe make AutowireCallable extend AutowireInline. Also, instead of a dedicated attribute for services built by factory, can't we add a factory argument to the AutowireInline attribute?

Sure I was unsure about the naming anyway though 🙈.

About the interface:
Wouldn't it rather make sense to keep it to allow extending / allowing custom Attributes that may create definitions that could be used for the Autowiring to be handled?

I'd rather see it as a benefit there allowing one to implement the interface and go for it instead of extending the base classes like the AutowireInline (after renaming). Unless this may be undesired anyway 🤷. Just being curious here.

Combining the factory thing into the AutowireInline seems fine as well since both are inlined 👍.

I made the adjustments according to your comment unless the one with the Interface. Just let me know if I still should remove it / rename it or whatever 👍.

And thanks for your fast feedback on this!

@DaDeather DaDeather force-pushed the 52819-dependency-injection-new-inline-attributes branch 6 times, most recently from b9abcbc to 48fc63b Compare December 1, 2023 07:42
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'm not convinced by the interface, I'd prefer removing it.
I'm generally not convinced by interfaces for value objects and attributes are value objects.
Extending the class makes sense to me in this case.

@DaDeather DaDeather force-pushed the 52819-dependency-injection-new-inline-attributes branch from 48fc63b to f21a70b Compare December 1, 2023 09:15
@DaDeather
Copy link
Contributor Author

I'm not convinced by the interface, I'd prefer removing it. I'm generally not convinced by interfaces for value objects and attributes are value objects. Extending the class makes sense to me in this case.

Alright 👍 I have removed the interface and based the AutowireCallable on the new AutowireInline as you proposed before.

@DaDeather DaDeather force-pushed the 52819-dependency-injection-new-inline-attributes branch from f21a70b to 917a698 Compare December 1, 2023 09:21
@DaDeather DaDeather changed the title [DependencyInjection] add InlineService and InlineFactory attributes to allow service configuration on class level [DependencyInjection] add AutowireInline attribute to allow service configuration on class level Dec 1, 2023
@stof
Copy link
Member

stof commented Dec 1, 2023

Please update the PR description to show usages of the feature. This makes it a lot easier for the documentation team (and also for reviewers)

@DaDeather DaDeather force-pushed the 52819-dependency-injection-new-inline-attributes branch 3 times, most recently from 5955b18 to 6a7e3fc Compare February 27, 2024 05:49
@nicolas-grekas
Copy link
Member

About my remaining concerns (ResolveNamedArgumentsPass, ResolveChildDefinitionPass and other pass not running), I'm wondering if we shouldn't add a new ResolveAutowireInlineAttributesPass that would scan autowired services for this attribute and would register definitions for each of them. Then, based on some naming convention (likely built on Container::hash($definition)), AutowirePass would just reference these definitions instead of calling AutowireInline::buildDefinition().

WDYT? Up to give it a try?

@DaDeather
Copy link
Contributor Author

About my remaining concerns (ResolveNamedArgumentsPass, ResolveChildDefinitionPass and other pass not running), I'm wondering if we shouldn't add a new ResolveAutowireInlineAttributesPass that would scan autowired services for this attribute and would register definitions for each of them. Then, based on some naming convention (likely built on Container::hash($definition)), AutowirePass would just reference these definitions instead of calling AutowireInline::buildDefinition().

WDYT? Up to give it a try?

Sure!
Could you maybe give a few hints how / where to implement this correctly?
I'm struggling a bit with the input style and the expected outcome here and therefore maybe didn't understand what's really expected here.

Would you expect a definition be like this:

class AutowireInlineAttributesBar {
    public function __construct(Foo $foo, string $someString)
    {
    }
}

class AutowireInlineAttributes
{
    public function __construct(
        #[AutowireInline(AutowireInlineAttributesBar::class, [
            '$foo' => Foo::class,
            '$someString' => 'testString',
        ])]
        public AutowireInlineAttributesBar $inlined,
    ) {
    }
}

Where the class Foo then would be a autowired as the provided service name and the someString injected as a string or what would you rather expect here to happen?

Besides that if I got you right I would have to create a class called ResolveAutowireInlineAttributesPass which then checks for a passed Definition to be $definition->isAutowired() === true and check for it to have any AutowireInline attributes to be processed (calling ->buildDefinition(...) and registering those via $this->container->setDefinition(ContainerBuilder::hash($autowireInlineAttributeInstance), $definition);.

Is this assumption correct?

@nicolas-grekas
Copy link
Member

You've got it right I think :)

@DaDeather
Copy link
Contributor Author

DaDeather commented Mar 7, 2024

You've got it right I think :)

I've prepared something that doesn't quite work 🤷 as mentioned before maybe I'm missing something here. Could you maybe take a look so that we may correct the issues there or my misunderstanding 🙈?

I would be grateful 👍 dd16390

@nicolas-grekas

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.

Thanks for the update, the path looks good to me.
Here are some comments to go to the next step.

* @author Ismail Özgün Turan <oezguen.turan@dadadev.com>
*/
#[\Attribute(\Attribute::TARGET_PARAMETER)]
class AutowireInline extends Autowire
Copy link
Member

@chalasr chalasr Mar 20, 2024

Choose a reason for hiding this comment

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

AutowireInline looks pretty cryptic, any newcomer wouldn't be able to get what the attribute is for by just looking at its name nor its description as it currently is. I think we either need to find a super self-explanatory name (I've no clue) or extend the description so that it tells what's the purpose of the attribute and when it should be used (inline service definition only means something to advanced Symfony's DIC hackers)

Copy link
Member

Choose a reason for hiding this comment

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

Naming things... :)
"inline" refers to "inline_service()" in the PHP-DSL

AutowireInlineService? but verbose
AutowireNew? AutowireObject? AutowireInstance? or keep AutowireInline?

of course, a top notch description is also desirable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a better description here feel free to request further suggestments to it 👍.

@DaDeather DaDeather force-pushed the 52819-dependency-injection-new-inline-attributes branch 3 times, most recently from 0ad67af to 5144cea Compare April 2, 2024 08:02
@nicolas-grekas nicolas-grekas force-pushed the 52819-dependency-injection-new-inline-attributes branch 2 times, most recently from 964fe78 to 19b1076 Compare April 22, 2024 13:48
@nicolas-grekas nicolas-grekas added Ready ❄️ Feature Freeze Important Pull Requests to finish before the next Symfony "feature freeze" and removed Status: Needs Work labels Apr 22, 2024
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.

PR is ready.
I added a second commit to make the implementation a bit more generic.
Reviews welcome 🙏

@@ -494,7 +494,9 @@ public function removeDefinition(string $id): void
{
if (isset($this->definitions[$id])) {
unset($this->definitions[$id]);
$this->removedIds[$id] = true;
if ('.' !== ($id[0] ?? '-')) {
Copy link
Member

Choose a reason for hiding this comment

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

I added this rule so that we don't use internal service ids in error messages. This matters to this PR because the added code creates new removed-ids, which means fixtures have to be updated with (what I consider as) noise.

@nicolas-grekas nicolas-grekas force-pushed the 52819-dependency-injection-new-inline-attributes branch from 19b1076 to b9a838e Compare May 2, 2024 08:54
@fabpot
Copy link
Member

fabpot commented May 2, 2024

Thank you @DaDeather.

@fabpot fabpot merged commit 66faca6 into symfony:7.1 May 2, 2024
3 of 10 checks passed
@DaDeather DaDeather deleted the 52819-dependency-injection-new-inline-attributes branch May 2, 2024 09:16
@fabpot fabpot mentioned this pull request May 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DependencyInjection Ready ❄️ Feature Freeze Important Pull Requests to finish before the next Symfony "feature freeze" Status: Reviewed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[DependencyInjection] Add new attributes to allow inlined services while using autowiring
6 participants