This issue is the 1st step towards migrating Field API towards Plugins.
Attached patch starts with widgets. Next in line, in separate patches:
- Formatters - basically ready in #1785748: Field formatters as plugins (build on the widgets patch), but needs more performance testing
- Fields types
- Field storage backends - or not, depending on how #1497374: Switch from Field-based storage to Entity-based storage goes
Work happens in the D8 Field API sandbox, in the field-plugins-widgets-1785256 branch.
Work leading to a core patch was coordinated in #1742734: [META] Widgets as Plugins (now marked as dupe of this one)
"Field widget" Plugin type
The patch introduces the "Field widget" plugin type, managed by WidgetPluginManager. It uses annotations discovery, and a custom WidgetFactory factory (as advised by EclipseGc rather than the ReflectionFactory, for performance reasons).
The WidgetPluginManager object is accessed through field_get_plugin_manager('widget'), which acts as a static factory for now. This will move to the DIC at some point (after #1708692: Simpletest slow and other problems following Bundle services aren't available in the request that enables the module is solved).
WidgetInterface / WidgetBase
Actual widget classes implement WidgetInterface, and will most probably want to subclass WidgetBase.
WidgetBase methods hold the code that currently exists in field_default_*():
WidgetInterface also includes methods replacing the old hook_field_widget_*() hooks:
|(complex tricks with FAPI #callbacks)||(new) Widgetinterface::massageFormValues()|
Those methods are usually not called directly, but rather called by the methods above.
To make it clearer for widget developpers which methods need to be implemented vs. which are probably better off inherhited from WidgetBase,
the interface has actually been split between WidgetBaseInterface (wrapper methods) and WidgetInterface (methods you actually want to implement - extends WidgetBaseInterface). Credits @EclipseGc for the suggestion.
Widget plugins are created from an $instance structure (since the $instance holds the definition of the widget). They are instanciated on demand (when a widget for that $instance is needed on the page) and are persisted through the request within the $instance itself. There is no real place other than the $instance where the Widget could be persisted, since a widget is "per instance".
This implies turning the $instance structure into a classed object.
- This is something we want to do long-term (move away from dumb-array-of-hell $field and $instance structs)
- This is something that we'll *need* to do anyway as part of #1735118: Change notice: Convert Field API to CMI ($field and $instance as ConfigEntities)
- This very much makes sense for our case here.
Yet, to limitate the impact on the external facing API (switching $instance['property'] to $instance->property or $instance->getProperty() across all of core would be an unreviewable 500k+ patch), we're just turning $instance to a
FieldInstance implements ArrayAccess class here. Credits @sun for the idea and implementation.
FieldInstance::getWidget() calls the plugin system to instantiate the Widget plugin, then persists it for the rest of the request.
Similarly, there will be a FieldInstance::getFormatter($view_mode) when we do "formatters as plugins"
This also means that we cannot support hook_field_widget_properties_alter() changing the widget type on the fly on a per-entity basis. The hook still works, but does not receive an $entity in its $context parameter. We discussed this with @EclipseGc and @chx (who filed the original use case for "entity by entity" in D7), and this was deemed a reasonable regression - see #1762828-10: Figure out hook_field_widget_properties_alter() for details.
Additionally, _field_info_prepare_instance_widget() is gone. Widget properties are not pre-massaged on all existing instances as part of _field_info_collate_field() anymore, but only when actually creating a widget opbject. Same will happen to _field_info_prepare_instance_display() when we do "formatters as plugins".
In order to:
- keep the patch size reasonable
- focus reviews on a limited amount of implementations
- crowdsource the rest of the conversions as potential "novice" followups, getting more people familiar with the API
this patch only converts a couple core widgets to the new API : text widgets, number widget, test widgets.
The other widgets (option widgets, image & file upload, taxo autocomplete, email) are kept fully functionnal by a LegacyWidget implementation, that takes care of proxying method calls to the old-style hooks. WidgetPluginManager discovers legacy widgets through a custom LegacyDiscoveryDecorator, that uses HookDiscovery to append the widget definitions still exposed through hook_field_widget_info(). Credits @EclipseGc for the idea.
- hook_field_widget_info() is replaced by annotations. Some keys are renamed (white spaces replaced by underscores).
Note : we currently have no standard location to document the expected format for the annotations - i.e. the documentation currently contained in the phpdoc for hook_field_widget_info().
- hook_field_widget_settings_form(), hook_field_widget_form(), hook_field_widget_error() : replaced by WidgetInterface methods (see above)
- field_default_form(), field_default_form_errors(), field_default_extract_form_values(), field_default_submit() : replaced by WidgetInterface methods (see above)
- hook_field_widget_properties_alter(), hook_field_widget_properties_ENTITY_TYPE_alter() : the 'entity' and 'default' entries in $context are gone.
Patch depends on:
#1751100: Bump minimum version of php required to 5.3.5 - our use of ArrayAccess requires PHP 5.3.4.
Testbots run 5.3.4 now, but the actual core requirement bump hasn't been committed yet
Patch currently includes:
#1778942: Discovery::getDefinition() / getDefinitions() : inconsistent return values for "no result" - to get rid of notices in tests
#1764380: Base class for management of Plugin 'settings' (a self contained version, that will be used within Field API plugins even if that issue goes nowhere)
Patch includes a workaround for:
#1764232: CacheDecorator provides no way to clear cached definitions - denoted by a @todo
The rest of the widget conversions will need:
#1705702: Provide a way to allow modules alter plugin definitions - option widgets need hook_field_widget_info_alter()
Benchmarks will be tracked at #1781250: Run performance tests.
Code-wise: @yched, @Stalski, @zuuperman, @sun, @tstoeckler - with xtra special thanks to @Stalski for awesome help on various subtasks.
Many thanks @EclipseGc for numerous feedback & suggestions on Plugin API.