Skip to content

[DependencyInjection] Add #[Constructor] attribute #57654

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

Conversation

maelanleborgne
Copy link
Contributor

@maelanleborgne maelanleborgne commented Jul 4, 2024

Q A
Branch? 7.2
Bug fix? no
New feature? yes
Deprecations? no
Issues Fix #53605 (comment)
License MIT

Description

This PR introduces a new #[Constructor] attribute to simplify the factory declaration using attributes.
Right now to declare a factory using attributes, you have to use #[Autoconfigure(constructor: 'staticMethod')] on the class.
This new #[Constructor], applied to a static method, will set it as the factory of the declaring class.

Example

class MyService
{
    #[Constructor]
    public static function create(/*...*/): self
    {
        /*...*/
    }
    /*....*/
}

Todo

I had some trouble finding a way to test this. There aren't many tests on autoconfiguration callbacks and most of them are copy/paste (AsMessageHandler autoconfiguration and its test for example). Any input on how to tests this or where the tests should live would be appreciated.

  • Update CHANGELOG.md
  • Add documentation
  • Cleanup tests

@carsonbot carsonbot added this to the 7.2 milestone Jul 4, 2024
@carsonbot carsonbot changed the title [DI] Add constructor attribute [DependencyInjection] Add constructor attribute Jul 4, 2024
@maelanleborgne maelanleborgne force-pushed the feature/constructor-attribute branch from b0f5ee8 to b04a840 Compare July 4, 2024 12:36
@symfony symfony deleted a comment from carsonbot Jul 4, 2024
@OskarStark OskarStark changed the title [DependencyInjection] Add constructor attribute [DependencyInjection] Add #[Constructor] attribute Jul 4, 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.

As I review this PR (which I suggested ;) ) I realize that this will force inspecting every methods of every services, while currently we need to inspect only classes.
I might be wrong because maybe inspecting all methods is already what we do in some commonly used bundle?
If not, then it might be better to add the attribute at the class level #[Constructor(method: 'create'], or at least before that to bench the impact that scanning all methods has in practice so that we don't decrease the DX by increasing the compilation time.
Up to dig this aspect?

if (!$reflector->isStatic()) {
throw new LogicException(sprintf('Cannot use "%s::%s()" as a constructor: the method should be static.', $reflector->class, $reflector->name));
}
$declaringClass = $reflector->getDeclaringClass();
Copy link
Member

Choose a reason for hiding this comment

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

by using the declaring class, we don't check the methods on the actual class

@@ -699,6 +700,28 @@ public function load(array $configs, ContainerBuilder $container): void
$container->registerAttributeForAutoconfiguration(AsSchedule::class, static function (ChildDefinition $definition, AsSchedule $attribute): void {
$definition->addTag('scheduler.schedule_provider', ['name' => $attribute->name]);
});
$container->registerAttributeForAutoconfiguration(Constructor::class, static function (ChildDefinition $definition, Constructor $attribute, \ReflectionMethod $reflector): void {
if (!$reflector->isStatic()) {
Copy link
Member

Choose a reason for hiding this comment

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

we should also check that the method is public

$reachableStaticMethods[] = $method->name;
if ($declaringClass->name !== $method->class || // If it was declared by a parent
$reflector->name === $method->name || // If it's the method we're processing
0 === \count($method->getAttributes(Constructor::class)) // If it doesn't have the attribute
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
0 === \count($method->getAttributes(Constructor::class)) // If it doesn't have the attribute
!$method->getAttributes(Constructor::class) // If it doesn't have the attribute

}
$factory = $definition->getFactory();
// Set the factory if it isn't set yet or if it's set to a method at the same/higher inheritance level
if (null === $factory || (\is_array($factory) && \in_array($factory[1], $reachableStaticMethods, true))) {
Copy link
Member

Choose a reason for hiding this comment

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

$factory cannot be set IIRC because $definition is empty here

@nicolas-grekas
Copy link
Member

Closing as this stalled. Please submit again once you could have a look at my previous comment. The points I raised there make the current approach a no-go IMHO.
Thanks for the PR!

@nicolas-grekas
Copy link
Member

For the record, this can be achieve using #[Autoconfigure(constructor: 'createInstance')] as introduced in #49665
My bad for #53605 (comment)

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.

3 participants