Problem/Motivation
Following #3021898: Support _defaults key in service.yml files for public, tags and autowire settings and #3049525: Enable service autowiring by adding interface aliases to core service definitions we are now in a place where we can use autowiring in core.
Steps to reproduce
Proposed resolution
In core.services.yml file set
services:
_defaults:
autowire: true
Remove as many explicit arguments sections as possible, as the container can infer the correct service in many cases.
Remaining tasks
Answer #48-#50
User interface changes
N/A
API changes
TBD
Data model changes
N/A
Release notes snippet
TBD
| Comment | File | Size | Author |
|---|---|---|---|
| #39 | 3295751-nr-bot.txt | 90 bytes | needs-review-queue-bot |
| #30 | 3295751-nr-bot.txt | 39.46 KB | needs-review-queue-bot |
| #28 | 3295751-nr-bot.txt | 90 bytes | needs-review-queue-bot |
Issue fork drupal-3295751
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 #3
wim leersThis is hard-blocked on #3049525: Enable service autowiring by adding interface aliases to core service definitions. See #3049525-53: Enable service autowiring by adding interface aliases to core service definitions for why this can't happen yet.
Comment #4
quietone commentedAdding postponed item to remaining tasks per remaining tasks summary field details.
Comment #5
wim leersCorrection for #3: this is probably blocked on #3320315: Allow uppercase service IDs/names — necessary for autowiring support instead.
Comment #6
wim leers#3320315: Allow uppercase service IDs/names — necessary for autowiring support is in already, which should make this highly doable now! 👍
Comment #7
andypost@Wim as I got it was postponed on #3049525: Enable service autowiring by adding interface aliases to core service definitions so not clear title vs status
Comment #8
longwaveThe blockers are in, we can work on this again.
Comment #9
longwaveProbably should also do #3325557: Enable more service autowiring by adding interface aliases to core modules first.
Comment #10
longwaveIt turns out that we can just start using the
#[Autowire]attribute now, and it works out of the box with Symfony 6.2. If autowiring fails the error message is unhelpful, but that is fixed in Symfony 6.3.Comment #11
longwaveComment #12
donquixote commentedComment #13
borisson_This looks really cool and I use this feature frequently in laravel/symfony projects so I am a big fan.
Since this also already converts a lot of services this would be really helpful to get in but I'm not sure how to review this. If this would be broken in some way this would lead to a lot of broken tests, so seeing that doesn't happen gives a lot of confidence.
We should probably add a change record as well?
Comment #14
smustgrave commentedI agree with the change record.
As someone who hasn't done any symfony project I didn't know what this was so there are sure to be others.
Comment #15
wim leers#3325557: Enable more service autowiring by adding interface aliases to core modules landed!
AFAICT that means this issue can now really move forward!
Comment #16
elberHi I added a basic CR https://www.drupal.org/node/3366757
Comment #18
longwaveOpened a new MR for 11.x, following #3325557: Enable more service autowiring by adding interface aliases to core modules hopefully everyone can see how this can simplify services going forwards. One day I hope we won't even need to write services.yml files in most cases.
Comment #19
smustgrave commentedSeems 11.x caused some failures.
Comment #20
mondrakeI filed #3367416: [policy] Service autowiring - decide approach for multiple service implementations based on the same class and different arguments. That would help here, by allowing to avoid the need to use
#[Autowire]for services that have multiple implementations for the same interface (cache bins, loggers, etc).Comment #21
longwaveFixed test failures and autowired some things that were previously missed.
This doesn't yet autowire core.services.yml, perhaps that can be done in a followup.
Comment #22
smustgrave commentedShould this be postponed on #3367416: [policy] Service autowiring - decide approach for multiple service implementations based on the same class and different arguments?
Comment #23
mondrake#22 no I do not think so. If it's finally agreed that #3367416: [policy] Service autowiring - decide approach for multiple service implementations based on the same class and different arguments is worth doing, then it's probably worth closing it and do what it preaches here, so that all things go together.
Comment #24
smustgrave commentedThink the open question on the other ticket needs to be answered first before closing, and I don't think I can make that call. But the question
Comment #25
smustgrave commentedDon't want to hold this up on #3367416: [policy] Service autowiring - decide approach for multiple service implementations based on the same class and different arguments if it's not needed. So lets see what the committers think.
Comment #26
quietone commentedThe issue summary is clear here. Reading the comments I don't see any unanswered question.
#21 suggests a followup. Does that need to be done or not?
The title of the CR is a directive. It reads like custom code must use autowiring. I think that needs a tweak. I read the CR and it seems vague to me. It says to 'remove as many explicit arguments as possible' without direction on what 'possible' means here. I also see in the MR the use of
Maybe an example should be added that uses that?
I am setting to NW because the MR needs a rebase.
Comment #27
elberComment #28
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 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 #29
longwaveThis is getting unwieldy to repeatedly fix merge conflicts; I wonder if we need to rescope this into smaller issues to get them in more easily.
Comment #30
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch 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 #31
wim leers+1 — what kind of split do you think would work well? 2 parts or more still?
Comment #32
longwaveOne way forward might be to write a test to discover which services could be autowired simply by removing their arguments section (ie. all arguments in the constructor can be autowired without specify attributes), which solves the simple cases, then in a followup we handle the more complicated cases where we need cache backends or loggers to be specified.
Comment #34
longwaveAdded a test case for #32.
Comment #35
longwaveRescoping this issue to cover core.services.yml only; the changes are minimal (almost all deletions) except for a new test that ensures we don't specify
argumentsin services.yml where autowiring can suffice.In followups, we can deal with autowiring more of core.services.yml (by either adding more interface aliases, or specifying the #[Autowire] attribute), and autowiring core modules as well.
Updated the change record to match the rescoped approach.
Comment #36
smustgrave commentedThe rescope seems good and definitely simpler to read.
Comment #38
longwaveNote for reviewers: the changes in LamguageServiceProvider imply that this is not backward compatible for modules that might implement similar service providers to extend or alter core services. I am not sure what we can do about this.
Comment #39
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 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 #40
mfbre: #38 what is the incompatibility? You no longer have to add an argument that can be autowired, but if you do, it still works, right?
Comment #41
longwaveIIRC the issue was that if we switch
language_managerto autowiring but leave the alter hook unchanged,config.factorybecomes the first argument of the new constructor, which is incorrect - it should belanguage.default.Comment #42
andypostblocker for #3296391: Allow defining injected services with PHP attributes
Comment #48
godotislateRebased against lastest 11.x and got tests green.
One issue that's come up, kind of similar to what's mentioned in #41: If you have a service definition that is autowired with no arguments in the definition, or if any of its service dependencies' definitions are are autowired with no arguments, then trying to instantiate that service in a module ServiceProvder will result in error. Take these two example from test module service providers:
While the definitions for both
statenormodule_handlerstill have arguments, if arguments forcache.backend.databaseare removed, then the$container->get()call fails with an error that 0 parameters are being passed into the constructor for DatabaseBackendFactory.This occurs because
ModifyServiceDefinitionsPasscompiler pass where the module ServiceProviders register and alter service definitions runs before the compiler passes to handle autowiring. Not sure how to handle this.Comment #49
longwaveWonder if we can just swap the order so autowiring runs very early, at least so service alterers get the arguments autowired? This may well have other side effects though...
Comment #50
godotislateI tried adding an additional AutowirePass right before ModifyServiceDefinitionsPass in CoreServiceProvider:
This led to a circular reference exception when running AutowireTest, and I did not investigate further:
One vague idea I had is to deprecate altering services via module service providers. Service decoration can handle the bulk of use cases for altering services. And if/when #3293926: Error decorating non-existent service when inner service's module not installed gets in, that should cover a lot of use cases of conditionally altering services.
Altering container parameters based on config like in LanguageServiceProvider might be something that be handled by custom compiler pass and tokenizing BootstrapConfigFactory::get() to use in YAML.
But I'm sure there are projects that do more complicated logic than that, so that might not cover enough use cases. It's also possible that projects implement ServiceProvider
register()with conditional logic to add service definitions based on whether a module is installed, in which case just removingalter()is insufficient.Comment #51
smustgrave commentedThis one was coming up on the 2 month mark for needs-review-queue but seems there ares still open questions in #48-50, so updating remaining tasks. Also not sure if this counts as an API change so left that as TBD.
Comment #52
godotislateFor the core services in scope to be autowired here, only a few were excepted from the conversion test and their definitions kept their arguments. Tests are green this way. 48 - 50 calls out the exception, and it's really a question of do we keep the exception in place and punt resolving 48 - 50 to one of the follow ups where we deal with more complicated autowiring conversions, or do we deal with it now. Also, a solution to how to deal with 48 - 50 does not currently exist.
Comment #53
godotislateI investigated #48-50 some more. Basically, there's a reason that the
AutowirePasshappens after a bunch of other passes in the "optimization" phase, never mind a few more in the "before optimization" phase, inSymfony\Component\DependencyInjection\Compiler\PassConfig: a lot has to be in place first in order for the service constructor arguments to be autowired correctly. Since Drupal'sModifyServiceDefinitionsPasshas to run near the very beginning, there's no way to autowire services before that without essentially running the bulk of the other passes. I think it'd work out to running almost all the passes, then running ModifyServiceDefinitionsPass, and finally running a lot of the same passes again. And even then, I'm not sure that's possible without a side effect or error.Technically this doesn't just affect core services. If
$container->get('my_service')is run in any module's ServiceProvider class, ifmy_serviceis defined as autowired with no arguments, the service will fail to load. This is probably mitigated, because the most common use case is getting the module handler and adding or altering a service based on whether another module is installed. So it's less likely that many other services, except for maybestateorconfig.factory, would be instantiated in module ServiceProviders.Anyway, I'm not sure how to move forward with autowiring core services, because the use of
->get()means that the whole dependency chain of services for the service that is being instantiated can not be autowired.Here's example output of the error during a test run:
Comment #54
godotislateComment #55
longwaveThanks for trying. Given that we can't know what existing service providers might want to do, I'm not sure how we can do this without breaking backward compatibility. Maybe we just don't need to do this issue at all, and core services remain manually wired.
We could perhaps opt-in certain types of service to autowiring, such as event subscribers, which are unlikely to be altered by service providers? But unsure if there's any real benefit to that.
Comment #56
godotislateYeah, aside from checking whether a module's installed, or some state value or config value, it occurs to me that people could be doing things like a custom environment detection service and altering a defined service based on what environment it is.
Core services probably need to remain manually wired, because determining which services would never or almost never be instantiated in a module's ServiceProvider is arbitrary guesswork, other than the event subscriber example, and even then, you never know.
Removing the blocker tag because it looks like #3296391: Allow defining injected services with PHP attributes was closed.
Comment #57
godotislateI had a thought on a possible way to move forward:
Since Drupal has its own ContainerBuilder class, can do the following:
::get()so that only certain services that are in an allow list and explicitly set as not autowired can be instantiated, maybe with a way to limit this restriction only to compile time?(core.builder_allowed_servicesor some better name), with modules being able to provide their own{module name}.builder_allowed_servicesparameters that get merged to the core oneBootstrapConfigStorageFactoryused only during compile timeCan create a new issue to look into the above if this seems worthwhile.
Comment #58
godotislateAfter a bit more thought, not sure the allow list is necessary if all the services being instantiated (and their dependencies) need to be explicitly marked
autowire: false.And even then, there's the risk that services in core that are deemed autowire-safe and have their arguments definition removed in YAML could be dependencies of services in contrib or custom code that are instantiated in those modules' service providers, leaving us back where we started. :)
Comment #59
ghost of drupal pastOne thing I should note: service providers should not, in general , do $container->get('foo') they should only do getDefinition/findDefinition, I noted at the end https://www.drupal.org/node/2974194 how to get a list of modules, how to access (raw) config and encouraged settings instead.
Comment #60
godotislateThere are 3 cases in core where
ContainerBuilder::get()is used.Two are of them are in test modules:
Drupal\container_rebuild_test\ContainerRebuildTestServiceProvider::alter()has$container->get('state')The use of module handler likely can be replaced with the container.modules parameter, however the use of state in the first test is trickier to replace. It'd probably be better for test performance to use key value there anyway, but that still requires instantiating the key value service.
The third is Drupal\workspaces\WorkspacesServiceProvider::alter(), which has
$container->get('kernel'). This last one can be ignored, because kernel is not autowired.Comment #61
godotislateAgreed. Thinking about this more, it might be worthwhile to do the allow list anyway, separate from autowiring concerns, to discourage devs from instantiating services in service providers, or at least make them think about it and provide them documentation for possible alternatives for what they want to do.