Problem/Motivation
After cataloging the source contexts in #3540247: Map source contexts with enums, lets tidy them
Proposed resolution
We can rely on the Core's mechanism, checking the data instead of the keys, by:
- never providing 2 or more contexts of the same type for a single source plugin
- forbidding the
anycontext and usingentityonly when not possible to target a specific entity type (most of the time, to target any fieldable content entity) - not being shy to create our own contexts with a context class or a typed data plugin if necessary
So, ContextAwarePluginManagerTrait will be able to load only the source plugins whose constraints are satisfied by the provide contexts.
Proposal: We may be able to do everything with only 4 contexts, while keeping it extensible for future usage:

Up-to-date source document: https://docs.google.com/spreadsheets/d/1-oAOLOEYaCaYhMPWKAuwNNTQ5YFeU_44...
We can start by implementing ContextProviderInterface on the existing "consumer" plugins. This interface is for services, but it seems a good fit also for plugins. It is what we already do in Display Builder.
ComponentBlock
Which is passing the current context (blocks are already context aware):
public function getRuntimeContexts(array $unqualified_context_ids) {
return $this->getContexts();
}EntityComponentBlock
Same with the (manual? automatic?) addition of entity coming from the display building tool (Layout Builder, at least).
Because entity is already available in ComponentBlock with no value. It may be the opportunity to remove EntityComponentBlock and its deriver.
ComponentFormatter
Currently providing ui_patterns:lang_code (type: any), ui_patterns:field:items (type: any) and field_granularity:items
Proposal:
public function getRuntimeContexts(array $unqualified_context_ids) {
$entity = $this->sampleEntityGenerator->get($this->fieldDefinition->getTargetEntityTypeId(), $this->fieldDefinition->getTargetBundle());
return [
'entity' => EntityContext::fromEntity($entity),
'field_name' => new Context(new ContextDefinition('string'), $this->fieldDefinition->getName()),
];
}
ComponentPerItemFormatter
Currently providing ui_patterns:lang_code (type: any), ui_patterns:field:items (type: any) and ui_patterns:field:index (type: integer)
Proposal (same as ComponentFormatter with the addition of an integer):
public function getRuntimeContexts(array $unqualified_context_ids) {
$entity = $this->sampleEntityGenerator->get($this->fieldDefinition->getTargetEntityTypeId(), $this->fieldDefinition->getTargetBundle());
return [
'entity' => EntityContext::fromEntity($entity),
'field_name' => new Context(new ContextDefinition('string'), $this->fieldDefinition->getName()),
'delta' => new Context(new ContextDefinition('integer'), 0),
];
}Then, delta can be dynamically updated
public function viewElements(FieldItemListInterface $items, $langcode) {
$build = [];
$contexts = $this->getAvailableContexts();
foreach ($items as $delta => $item) {
$contexts['delta'] = new Context(new ContextDefinition('integer'), $delta);
$build[] = $this->buildComponentRenderable($this->getComponentConfiguration()['component_id'], $contexts);
}
return $build;
}ComponentLayout
Currently providing ui_patterns:form_state (type: any), entity (type: entity).
Proposal: Nothing?
ComponentStyle
Currently providing ui_patterns_views:rows (type: any). And ui_patterns_views:view (type: any), ui_patterns_views:plugin:options (type: any), ui_patterns_views:plugin:display_handler (type: any), ui_patterns_views:view_entity (type: entity:view) through ViewsPluginUiPatternsTrait.
Proposal:
public function getRuntimeContexts(array $unqualified_context_ids) {
return [
'view' => EntityContext::fromEntity($this->view->storage),
'display' => new Context(new ContextDefinition('string'), $this->view->current_display),
];
}ComponentRow
Currently providing ui_patterns_views:row:index (type: integer), entity (type: entity). And ui_patterns_views:view (type: any), ui_patterns_views:plugin:options (type: any), ui_patterns_views:plugin:display_handler (type: any), ui_patterns_views:view_entity (type: entity:view) through ViewsPluginUiPatternsTrait.
Proposal (same as ComponentStyle with the addition of an integer):
public function getRuntimeContexts(array $unqualified_context_ids) {
return [
'view' => EntityContext::fromEntity($this->view->storage),
'display' => new Context(new ContextDefinition('string'), $this->view->current_display),
'position' => new Context(new ContextDefinition('integer'), $this->position ?? 0),
];
}Context switchers
There are the source plugins actually extending DerivableContextSourceBase:
EntityFieldSource(entity_field) ([Entity] ➜ [Field]), without a deriver class?EntityReferencedSource(entity_reference) ([Entity] ➜ Referenced [Entity])EntityReferenceFieldPropertySource(entity:field_property) ([Field item] ➜ Referenced [Entity])
and its EntityReferenceFieldPropertyDerivableContextDeriver
They can be managed as source plugins implementing both:
ContextAwarePluginInterfacelike the other context aware source pluginsContextProviderInterfacelike the configurable renderable plugins
For example, EntityFieldSource is aware of:
context_definitions: [
'entity' => new ContextDefinition('entity')
],and is providing:
public function getRuntimeContexts(array $unqualified_context_ids) {
return [
'entity' => $this->getContextValue('entity'),
'field_name' => new Context(new ContextDefinition('string'), $this->getSetting('derivable_context')),
];
}
Let's say we have a source selector where the root level contexts are: entity + field name, so for example in ComponentFormatter. Because I am in the root, I want all sources, , so for a slot:
- without contexts, so for a slot: Component, Wysiwyg...
- with only
entitycontext: Entity Link, "Entity -> Field"... - with only field name context: none.
- with both contexts: Field Formatter, Field Label...
Now I am switching contexts thanks to "Entity -> Field", my context is also entity + field name with the same entity but a different field name, so i will access to the same sources:
- without contexts: Component, Wysiwyg...
- with only
entitycontext: Entity Link, "Entity -> Field"... - with only field name context: none.
- with both contexts: Field Formatter, Field Label...
But i don't want all of them here, I just switched contexts, I want only the ones which differ from the original context:
- with both contexts: Field Formatter, Field Label...
It works like that today, that's great, we need to keep this.
Alternative proposal with custom typed data plugins
We don' t need to create specific context data (so specific Typed Data plugins) with the current proposal, but it may be a good way of reducing the ambiguity of entity, string and integer.
Ecosystem
What about the other modules (DS, Field Group...)? Are we breaking them? Anyway, it will be a change for the next major version.
Comments