Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
FormBase
is a patchwork of expectations for dependency injection.
It claims to implement ContainerInjectionInterface
, but that's only so it can provide a template for subclasses. Rather than using create()
to properly inject the services it offers, it only returns new static();
and then uses the anti-pattern \Drupal
to access other services.
Proposed resolution
- Have the
create()
method actually inject the services supported byFormBase
. - Add documentation to the docblock of
create()
so that devs can learn how to do proper DI, and so they remember to callparent::create()
. - Mark getters and setters for services as deprecated: #2494709: Deprecate dependency setters and getters on FormBase
Remaining tasks
User interface changes
API changes
Data model changes
Comment | File | Size | Author |
---|---|---|---|
#16 | interdiff.txt | 626 bytes | Mile23 |
#16 | 2674508_15.patch | 1.29 KB | Mile23 |
Comments
Comment #2
tstoecklerSo this was intentionally done so that sub-classes could more easily use
ContainerInjectionInterface
.I don't personally agree with that, but that was the reason at the time.
Comment #3
dawehnerYeah, it was always a reason purely driven by TX, IMHO.
In general we cannot inject anything for those base classes, because this would break a hell lot of implementations out there
Comment #4
tim.plunkettWe can inject it, but only if we still fall back.
IMO this will confuse people about whether they need to call
parent::__construct()
(they don't have to)I believe we should trait-ify the remaining service wrappers in FormBase and be done with it.
Honestly the only reason I extend FormBase is to skip an empty validateForm(), which is a terrible reason, but so it goes.
Comment #5
Mile23Yup.
The traits are neeto, but for instance
StringTranslationTrait
allows me to take a separate step tosetStringTranslation()
in my factory method, but thengetNumberOfPlurals()
ignores injected services anyway. That's out of scope here, but yah.Also the other traits on
FormBase
have the same problem: If there's no injected service they use\Drupal
when they should really complain.So then the basic idea of
FormBase
is a little broken. All it gives you is the knowledge that you should implementFormInterface
and then holds your hand if you did a bad job of dependency injection.As it stands, it's a very poor example of how to implement
ContainerInjectionInterface
, because the router is going to hand you a container, which you ignore and then infer from\Drupal
because it works.In that case, let's document that in the docblock.
Comment #7
daffie CreditAttribution: daffie commentedI f we want to do container injection the correct way we are making an API change and breaking backwards compatibility. So we cannot do that. Want we can do is adding a waning how container injection should be done. And the current patch has that waning. For me this is RTBC.
Comment #8
tim.plunkettThis issue needs a new title before commit.
Comment #9
daffie CreditAttribution: daffie commented@tim.plukett: You are right, thanks.
Comment #10
Mile23It's a doc change so 8.1.x.
I saw my own typo, so I fixed it, and then changed the docblock a bit. Here's the new one, easier to read here than in the patch:
Comment #11
Mile23Comment #12
daffie CreditAttribution: daffie commentedSo is the warning text even better.
Comment #14
Mile23Setting back to RTBC after spurious testbot fail.
Comment #15
alexpottThat's a lot of commas. How about:
Alternately, do not use FormBase. A class can implement FormInterface, use the traits it needs, and inject services from the container as required.
Comment #16
Mile23Changes per #15.
Comment #17
daffie CreditAttribution: daffie commentedAlexpott got what he wished for, so back to RTBC.
Comment #18
alexpottCommitted 2ca66f6 and pushed to 8.1.x and 8.2.x. Thanks!