Problem/Motivation

We already have a \Drupal\Core\DependencyInjection\AutowireTrait class, suitable for wiring dependencies into classes extending \Drupal\Core\DependencyInjection\ContainerInjectionInterface, implemented in #3394870: Allow controller service wiring via constructor parameter attributes

I think we should have a similar thing for \Drupal\Core\Plugin\ContainerFactoryPluginInterface, so we can easily skip adding a create() method for plugins.

I acknowledge we also have something similar #3294266: Improve plugin autowiring performance by moving reflection to discovery time, but this is useful in the short term for improving DX, before removing the requirement for ContainerFactoryPluginInterface. I guess the proposed new autowire trait would be removed from drupal alongside ContainerFactoryPluginInterface, if ever that happens. So I dont see this as redundant code.

Proposed resolution

  • Add a AutowireTrait for plugins, so they dont need to implement create().
  • Autowire the BlockContentBlock plugin to prove it works.

Remaining tasks

  • Tests.
  • Should we switch any non-tests core usages now? My suggestion is not, because I imagine there will be a huge LOC change here (many LOC removals, for the better!). But create methods could also be doing other things, or not all dependencies autowirable yet.

User interface changes

Nil.

API changes

New trait

Data model changes

Nil

Release notes snippet

Issue fork drupal-3452852

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

dpi created an issue. See original summary.

dpi’s picture

Title: Add a Autowire trait for plugins » Add an Autowire trait for plugins

dpi’s picture

Assigned: dpi » Unassigned
Issue tags: +Needs tests

Drive by code drop. Not working on this for now.

arnested’s picture

Nice. I was looking for exactly this today!

I have tested the code together with Drupal 10.3 and can confirm it works.

Since I wanted to have the benefit of this right away, I took the liberty and made it a small module: autowire_plugin_trait.

quietone’s picture

Version: 11.0.x-dev » 11.x-dev
mondrake’s picture

Also see #3464357: Add a trait for autowiring properties in tests.

There I’m suggesting to add symfony/expression-language as a dependency.

That would nicely allow using expressions and enable autowiring of constructs like

#[AutowireProperty(expression: "service('entity_type.manager').getStorage('node')")]

longwave’s picture

Status: Active » Needs review

Can we just extend the existing AutowireTrait to work here?

  public static function create(ContainerInterface $container) {
    $args = [];

becomes

  public static function create(ContainerInterface $container, ...$args) {

ie. additional arguments passed to create() become the initial set of arguments? We can array_slice() the reflected parameters so we don't try to fill in the ones we already have.

longwave’s picture

No, #8 doesn't work because ContainerFactoryPluginInterface::create() doesn't match.

longwave’s picture

Added AutowirePluginTrait to Views' PluginBase to enable autowiring by default (simply omit the create() method) and autowired some Views plugins to test that it all works.

longwave’s picture

Issue tags: -Needs tests

Not sure we need explicit tests, given we are exercising it in Views tests now, so removing the tag.

Wondering if \Drupal\Component\Plugin\PluginBase should just implement it directly so all plugins benefit?

longwave’s picture

Adding the trait to \Drupal\Core\Plugin\PluginBase (not Component) seems to work, I autowired the BlockContentBlock plugin to prove it.

smustgrave’s picture

Status: Needs review » Needs work

Shouldn't there be a test coverage for the new exception being thrown?

 throw new AutowiringFailedException($service, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s::_construct()", you should configure its value explicitly.', $service, $parameter->getName(), static::class));
longwave’s picture

Status: Needs work » Needs review

Added coverage for success and failure of autowiring a block plugin.

In a followup I think we could try making a base trait for AutowireTrait and AutowirePluginTrait where the common code can be abstracted away.

longwave’s picture

In fact let's do the refactor in #14 now to avoid copy-pasting the code here.

mstrelan’s picture

Regarding the phpstan baseline, it turns out that phpstan will accept @return annotations, and we already have that in ContainerFactoryPluginInterface, but PluginBase does not yet implement that interface. Have pushed a commit to implement that interface and update the baseline.

longwave’s picture

Status: Needs review » Needs work

Unfortunately implementing ContainerFactoryPluginInterface in PluginBase breaks a bunch of tests, I don't think Migrate plugins can cope with this as they have a slightly different constructor.

mstrelan’s picture

Status: Needs work » Needs review
longwave’s picture

Rebased.

larowlan’s picture

smustgrave’s picture

Status: Needs review » Needs work

This one needs a rebase.

mortona2k made their first commit to this issue’s fork.

mortona2k’s picture

Status: Needs work » Needs review

Only change is core/.phpstan-baseline.php.

I pushed in a new branch, is it preferred to do a force push on the PR, or merge into the branch and avoid overriding it?

smustgrave’s picture

Status: Needs review » Needs work

There was already an existing branch pointing to the right branch that feedback has been made on. That should be used.

Phpstan should be regenerated

dydave made their first commit to this issue’s fork.

dydave changed the visibility of the branch 3452852-autowire-trait-plugins-rebase to hidden.

dydave’s picture

Status: Needs work » Needs review

As per #25:

Conflicts were resolved in the initial merge request MR!8306 when the branch was rebased on 11.x.

Switching back to Needs review.

Thanks in advance!

smustgrave’s picture

Status: Needs review » Needs work

Pipeline appears to have conflicts. Not sure if the baseline file was manually fixed but it should just be re-generated

shalini_jha made their first commit to this issue’s fork.

shalini_jha’s picture

Status: Needs work » Needs review

As Mentioned in #30 , i have fixed the pipeline failure by generating the baseline. pipeline is passing , so moving this Back to NR.

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs change record

Believe close think one of the last pieces is to add a CR.

Example not super clear in core/modules/block_content/src/Plugin/Block/BlockContentBlock.php why

#[Autowire(service: 'block_content.uuid_lookup')]
Is needed but the others aren't, think a CR should clear that up.

larowlan’s picture

danielveza made their first commit to this issue’s fork.

danielveza’s picture

Rebased the MR, fixed conflicts, and brought in the Autowire changes from #3464426: AutowireTrait and autowire for services behave differently for nullable types to the new trait

danielveza’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record

Drafted a CR. Mostly based off the original Autowire change record but with references & examples updated.

znerol’s picture

Status: Needs review » Needs work

#37 links to the wrong CR. I think it should be that one. And the CR links back to the wrong issue.

Also left a couple of comments in the MR.

znerol’s picture

I'm confused, #38 points to the wrong CR as well. The correct one would be this.

danielveza’s picture

Ah you're right thank you, I've updated #37 to point to the correct CR

danielveza’s picture

All MR feedback addressed outside of one item that I've just asked for clarity on before I action.

danielveza’s picture

Status: Needs work » Needs review

All feedback done. Ready for review again.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

acbramley made their first commit to this issue’s fork.

acbramley’s picture

Status: Needs work » Needs review
Issue tags: +no-needs-review-bot
znerol’s picture

Issue summary: View changes
Status: Needs review » Needs work

Thank you.

I was wondering why only the content block was converted and found the answer in the issue comments. Added that info to the issue summary.

Left a question and a suggestion in MR comments.

danielveza’s picture

Status: Needs work » Needs review

Feedback addressed.

znerol’s picture

Thank you! I left one small suggestion.

longwave’s picture

I'm not 100% happy with the "AutowireArguments" name, if anyone has better suggestions feel free to offer them!

mxh’s picture

Maybe either one of

  • AutowiredConstructorTrait
  • AutowiredInstanceTrait

and the static method to be named as
public static function autowiredInstance()

?

znerol’s picture

I like the suggestions in #50. I'd probably prefix the method with create which is common for factory methods. E.g., createAutowired() or createAutowiredInstance().

danielveza’s picture

I'd probaby vote for AutowiredInstanceTrait & createAutowiredInstance if we're going to change this. I'll wait for consensus before spending time on this.

znerol’s picture

#52 is fine with me.

mxh’s picture

Side note, the instance itself is not "autowired", but that's what "createAutowiredInstance" might wrongly indicate. Therefore I'd favor "createInstanceAutowired" but that's also really just a micro-nit-pick now (let's just ignore this comment 😃)

znerol’s picture

I'm okay with #54 as well.

elc made their first commit to this issue’s fork.

elc’s picture

Rebased & renamed the trait as per #50/#52/#54. Tests are happy.

AutowiredInstanceTrait::createInstanceAutowired

znerol’s picture

Title: Add an Autowire trait for plugins » Add create() factory method with autowired parameters to PluginBase
Status: Needs review » Reviewed & tested by the community

Reviewed this in context. Adapted the issue title and the CR to match the actual implementation.

There is a slight risk that the newly added create() method in PluginBasebreaks existing contrib/custom plugins with an incompatible create() method in the same way as MenuLinkMock in this issue. Authors of affected plugin classes may rebase onto Drupal\Component\Plugin\PluginBase in that case.

longwave’s picture

One minor simplification that I am equally happy to skip, otherwise this looks good to me, but I am not eligible to commit as I worked on it.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed d7a61c2 and pushed to 11.x. Thanks!

Now that this issue is closed, please review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, please credit people who helped resolve this issue.

  • alexpott committed d7a61c29 on 11.x
    Issue #3452852 by dpi, longwave, smustgrave, mstrelan, mortona2k, dydave...
longwave’s picture

Thanks! Opened #3552110: Remove manual create() method from plugins as a followup so we can remove a bunch of code from core.

nicxvan’s picture

Did we benchmark this?

If there a lot of plugins, this is a lot of reflection runtime.

I'm not positive it's an issue, but it would be nice to have at least some confirmation we are not shooting ourselves in the foot performance wise.

longwave’s picture

@nicxvan it's only used if the plugin explicitly implements ContainerFactoryPluginInterface and doesn't implement create() itself, otherwise the reflection factory method is never called, so it should make no difference until people want to actually use it. The benchmarking can happen in the followup issue I think.

alexpott’s picture

I merged this and then had a thought about performance. Do we know how this will affect a page where maybe 100 plugins have to be instantiated? Obviously it'll be easier to see after #3552110: Remove manual create() method from plugins

longwave’s picture

Now we've done this I think we can also simplify #3294266: Improve plugin autowiring performance by moving reflection to discovery time to carry out the reflection at discovery time and store the autowiring configuration in the plugin definition, then we won't need reflection at runtime.

longwave’s picture

dpi’s picture

Found a high-level PHPStan regression when using this codeset.

Created #3554909: Narrowed typing regression for plugins.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

m.stenta’s picture

Does anyone know if there is a similar issue open for classes that implement ContainerDeriverInterface? Should we start one?

m.stenta’s picture

I also noticed that \Drupal\Core\Menu\LocalTaskDefault does not extend from \Drupal\Core\Plugin\PluginBase, but instead extends from \Drupal\Component\Plugin\PluginBase, so it doesn't inherit the create() method. Was there a reason for this, or just an oversight? Happy to open a new follow-up issue and MR to fix that if that's a good next step...

m.stenta’s picture