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
| Comment | File | Size | Author |
|---|
Issue fork drupal-3452852
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
Comment #2
dpiComment #4
dpiDrive by code drop. Not working on this for now.
Comment #5
arnested commentedNice. 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.
Comment #6
quietone commentedComment #7
mondrakeAlso see #3464357: Add a trait for autowiring properties in tests.
There I’m suggesting to add
symfony/expression-languageas a dependency.That would nicely allow using expressions and enable autowiring of constructs like
#[AutowireProperty(expression: "service('entity_type.manager').getStorage('node')")]Comment #8
longwaveCan we just extend the existing AutowireTrait to work here?
becomes
ie. additional arguments passed to
create()become the initial set of arguments? We canarray_slice()the reflected parameters so we don't try to fill in the ones we already have.Comment #9
longwaveNo, #8 doesn't work because
ContainerFactoryPluginInterface::create()doesn't match.Comment #10
longwaveAdded 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.Comment #11
longwaveNot 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?
Comment #12
longwaveAdding the trait to \Drupal\Core\Plugin\PluginBase (not Component) seems to work, I autowired the BlockContentBlock plugin to prove it.
Comment #13
smustgrave commentedShouldn't there be a test coverage for the new exception being thrown?
Comment #14
longwaveAdded 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.
Comment #15
longwaveIn fact let's do the refactor in #14 now to avoid copy-pasting the code here.
Comment #16
mstrelan commentedRegarding the phpstan baseline, it turns out that phpstan will accept
@returnannotations, and we already have that inContainerFactoryPluginInterface, butPluginBasedoes not yet implement that interface. Have pushed a commit to implement that interface and update the baseline.Comment #17
longwaveUnfortunately 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.
Comment #18
mstrelan commentedComment #19
longwaveRebased.
Comment #20
larowlanComment #21
smustgrave commentedThis one needs a rebase.
Comment #24
mortona2k commentedOnly 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?
Comment #25
smustgrave commentedThere was already an existing branch pointing to the right branch that feedback has been made on. That should be used.
Phpstan should be regenerated
Comment #28
dydave commentedAs 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!
Comment #30
smustgrave commentedPipeline appears to have conflicts. Not sure if the baseline file was manually fixed but it should just be re-generated
Comment #32
shalini_jha commentedAs Mentioned in #30 , i have fixed the pipeline failure by generating the baseline. pipeline is passing , so moving this Back to NR.
Comment #33
smustgrave commentedBelieve 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.
Comment #34
larowlanComment #36
danielvezaRebased 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
Comment #37
danielvezaDrafted a CR. Mostly based off the original Autowire change record but with references & examples updated.
Comment #38
znerol commented#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.
Comment #39
znerol commentedI'm confused, #38 points to the wrong CR as well. The correct one would be this.
Comment #40
danielvezaAh you're right thank you, I've updated #37 to point to the correct CR
Comment #41
danielvezaAll MR feedback addressed outside of one item that I've just asked for clarity on before I action.
Comment #42
danielvezaAll feedback done. Ready for review again.
Comment #43
needs-review-queue-bot commentedThe 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.
Comment #45
acbramley commentedComment #46
znerol commentedThank 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.
Comment #47
danielvezaFeedback addressed.
Comment #48
znerol commentedThank you! I left one small suggestion.
Comment #49
longwaveI'm not 100% happy with the "AutowireArguments" name, if anyone has better suggestions feel free to offer them!
Comment #50
mxh commentedMaybe either one of
and the static method to be named as
public static function autowiredInstance()?
Comment #51
znerol commentedI like the suggestions in #50. I'd probably prefix the method with
createwhich is common for factory methods. E.g.,createAutowired()orcreateAutowiredInstance().Comment #52
danielvezaI'd probaby vote for
AutowiredInstanceTrait&createAutowiredInstanceif we're going to change this. I'll wait for consensus before spending time on this.Comment #53
znerol commented#52 is fine with me.
Comment #54
mxh commentedSide 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 😃)
Comment #55
znerol commentedI'm okay with #54 as well.
Comment #57
elc commentedRebased & renamed the trait as per #50/#52/#54. Tests are happy.
AutowiredInstanceTrait::createInstanceAutowiredComment #58
znerol commentedReviewed 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 inPluginBasebreaks existing contrib/custom plugins with an incompatiblecreate()method in the same way asMenuLinkMockin this issue. Authors of affected plugin classes may rebase ontoDrupal\Component\Plugin\PluginBasein that case.Comment #59
longwaveOne 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.
Comment #60
alexpottCommitted d7a61c2 and pushed to 11.x. Thanks!
Comment #63
longwaveThanks! Opened #3552110: Remove manual create() method from plugins as a followup so we can remove a bunch of code from core.
Comment #64
nicxvan commentedDid 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.
Comment #65
longwave@nicxvan it's only used if the plugin explicitly implements
ContainerFactoryPluginInterfaceand doesn't implementcreate()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.Comment #66
alexpottI 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
Comment #67
longwaveNow 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.
Comment #68
longwaveImplemented reflection at discovery time in https://git.drupalcode.org/project/drupal/-/merge_requests/13493/diffs over in #3294266: Improve plugin autowiring performance by moving reflection to discovery time
Comment #69
dpiFound a high-level PHPStan regression when using this codeset.
Created #3554909: Narrowed typing regression for plugins.
Comment #71
m.stentaDoes anyone know if there is a similar issue open for classes that implement
ContainerDeriverInterface? Should we start one?Comment #72
m.stentaI also noticed that
\Drupal\Core\Menu\LocalTaskDefaultdoes not extend from\Drupal\Core\Plugin\PluginBase, but instead extends from\Drupal\Component\Plugin\PluginBase, so it doesn't inherit thecreate()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...Comment #73
m.stentaI wasn't able to find existing issues, so I opened two follow-ups:
#3565338: Add create() factory method with autowired parameters to DeriverBase
#3565337: Extend \Drupal\Core\Menu\LocalTaskDefault from \Drupal\Core\Plugin\PluginBase