Problem/Motivation
After visiting @alexpott's session "Drupal 9 is coming: Getting your code ready
" (https://events.drupal.org/seattle2019/sessions/drupal-9-coming-getting-y...) in Seattle, I wanted to make the contrib select2 module more BC stable by preventing constructor overwrites.
I found out that I am not able to prevent constructor overwriting for the Select2EntityReferenceWidget
(https://git.drupalcode.org/project/select2/blob/8.x-1.x/src/Plugin/Field...) because none of it's base classes is implementing the ContainerFactoryPluginInterface
.
The base class chain of this class is Select2EntityReferenceWidget -> Select2Widget -> OptionsSelectWidget -> OptionsWidgetBase -> WidgetBase
. That means if one of this base classes changes it's constructor, I will have a breakage in Select2EntityReferenceWidget
.
Proposed resolution
Add ContainerFactoryPluginInterface
to Drupal\Core\Field\WidgetBase
and Drupal\Core\Field\FormatterBase
and implement the ::create()
method in them. The big advantage is that all child classes will be more BC stable.
Remaining tasks
Discuss
Implement
Test
Commit
User interface changes
n.a.
API changes
Add ContainerFactoryPluginInterface to Drupal\Core\Field\WidgetBase
Data model changes
n.a.
Release notes snippet
n.a.
Comment | File | Size | Author |
---|---|---|---|
#7 | interdiff-3051633-2-7.txt | 1.4 KB | chr.fritsch |
#7 | 3051633-7.patch | 2.82 KB | chr.fritsch |
#2 | 3051633-2.patch | 1.43 KB | chr.fritsch |
Comments
Comment #2
chr.fritschHere is a first patch.
We could also look at all the child classes and clean up their constructors by using the ::create() method correctly.
Comment #3
alexpott@chr.fritsch it is okay in core to override a constructor - after all we can change the lot when something changes. But I think that what this issue is doing - making the deepest base class implement ::create is a good idea because it allows contrib and custom to extend any of core's concrete implementations safe in the knowledge that a core change is not going to break them because constructors of concrete plugins are not covered by our BC promise whereas the ::create() method is.
Comment #4
daniel.bosen+1 for this, WidgetBase and its descendants are overwritten a lot in contrib and custom code.
@alexpott I get that it is - in theory - OK for core to override constructors. But core code should be an important source for best practices, and it would be very helpful, if core overrides would also do the things you talked about in your session. I think the main reason for custom code to be overriding the constructor is, that people looked at how core does it...
Comment #5
BerdirWas surprised that this even works but we did add explicit support for this interface in the custom createInstance() implementation in \Drupal\Core\Field\WidgetPluginManager::createInstance().
One reason to make sure of that would be to inject the dependencies that the class is using her, like \Drupal::token() and module handler.
Also, reminder for #3030640: [policy, documentation] Clarify Constructor BC policy and document best practices for subclassing other classes, we don't really have an documentation or policy on how to use that DI approach that alex talked about in his presentation ;)
Comment #6
hchonov+1, but if we do it for widgets it would be good to be consistent and do it for field formatters as well :)
Comment #7
chr.fritschSo here is a new patch that also implements the interface in the formatter base class.
Comment #8
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThe patch looks good to me, thanks for updating
FormatterBase
as well :)How about @Berdir's feedback from #5? Should we inject the
module_handler
andtoken
services in this issue? We would need to do that in a BC-compatible way, otherwise all the widget classes that override the constructor would break. Alternatively, we could use setter injection for those two services in the base class...Comment #9
chr.fritschHm,
adding
module_handler
andtoken
to the constructor ofWidgetBase
feels a bit awkward. I checked all child classes and none of them is using these two services.So we could inject these services to
WidgetBase
by setter injection like:The downside is we'll break all child classes, which already have their own ::create() method.
In order to fix that, I would propose to introduce two private methods for getting services like:
We could use that function in WidgetBase and we adopt all child classes, with an implemented ::create() method, to call
$widget = parent:static()
.Comment #10
alexpottI think we should tackle inject of the services the class is already using in a follow-up. Adding a create doesn't make it easier for core to add or change dependencies - it makes it easier for contrib and custom.
Comment #11
chr.fritschHere is the follow-up #3059023: [PP-1] Use proper DI for all used services in WidgetBase and FormatterBase
Comment #12
chr.fritschI also created a CR: https://www.drupal.org/node/3059039
Comment #13
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedOk, I don't mind tackling the existing service usages in a followup :)
Comment #14
catchCommitted fcb657b and pushed to 8.8.x. Thanks!
Comment #17
quietone CreditAttribution: quietone at PreviousNext commentedPublish the change record