Problem/Motivation
Interesting stuff is happening on Display Builder side: #3579299: Tidy context management which can inspire us to clean the context on UI Patterns side.
UI Patterns has a very specific way of dealing with contexts:
- Derivable context plugin type:
- src/Attribute/DerivableContext.php
- src/DerivableContextInterface.php
- src/DerivableContextPluginBase.php
- src/DerivableContextPluginManager.php (and the related
plugin.manager.ui_patterns_derivable_contextservice) - src/EventSubscriber/UiPatternsEntitySchemaSubscriber.php (and the related
ui_patterns.entity_schema_subscriberservice) - src/Plugin/Derivative/DerivableContextDeriver.php
- src/Plugin/UiPatterns/DerivableContext/EntityFieldDerivableContext.php
- src/Plugin/UiPatterns/DerivableContext/EntityReferencedDerivableContext.php
- Requirements context plugin:
- src/Plugin/Context/RequirementsContext.php
- src/Plugin/Context/RequirementsContextDefinition.php
- the attribute in
\Drupal\ui_patterns\Attribute\Source
- Resolvers:
- src/Resolver/ChainContextEntityResolver.php (and the related
ui_patterns.chain_context_entity_resolverservice) - src/Resolver/ChainContextEntityResolverInterface.php
- src/Resolver/ContextEntityResolverInterface.php
- src/Resolver/FieldLayoutContextEntityResolver.php
- src/Resolver/LayoutBuilderContextEntityResolver.php
- tests/src/Kernel/Resolver/ChainContextEntityResolverTest.php
- src/Resolver/ChainContextEntityResolver.php (and the related
- A specific constraint plugin used in
ComponentFormatter:- src/Plugin/Validation/Constraint/RequiredArrayValuesConstraint.php
- src/Plugin/Validation/Constraint/RequiredArrayValuesConstraintValidator.php
- A trait used by SourcePluginManager:
- src/ContextMatcherPluginManagerInterface.php
- src/ContextMatcherPluginManagerTrait.php
- and maybe more...
The main part of this mechanism is the context_requirements attributes which has been added because Core's ContextAwarePluginManagerTrait::getDefinitionsForContexts() (which is the reference implementation of ContextAwarePluginManagerInterface) doesn't check the context key, only the context data, as the Context API expects us to do.
It serves us well for 2 years, but is is complex.
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 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. However, let's check what contrib space is already proposing before creating our own.
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')),
];
}
Ecosystem
What about the other modules (DS, Field Group...)? Are we breaking them? Anyway, it will be a change for the next major version.
| Comment | File | Size | Author |
|---|---|---|---|
| #14 | Capture d’écran du 2026-05-05 14-50-04.png | 102.8 KB | pdureau |
Issue fork ui_patterns-3540247
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 #2
pdureau commentedComment #3
pdureau commentedComment #4
pdureau commentedComment #5
pdureau commentedComment #6
pdureau commentedComment #7
pdureau commentedComment #8
pdureau commentedI have created the fork, just to mess around ;)
Comment #9
pdureau commentedComment #10
pdureau commentedComment #11
pdureau commentedComment #12
pdureau commentedComment #13
pdureau commentedComment #14
pdureau commentedComment #15
pdureau commentedA little review of the current
context_requirementsvalues and how we can replace them with Drupal Core contexts:field_formatter(in FieldLabelSource): ✅ it was never used, let's drop itviews:style(ViewRowsSource and many Display Builder sources): ✅ replaced byentity:view+stringviews:row(in ViewFieldSource): ✅ replaced byentity:view+string+integerOnly in Display Builder:
layout_discovery: ✅ nevermind, we will drop it anyway in #3571009: Add SourceWithSlotsInterface and LayoutSourcepage(in PageTitleSource, MainPageContentSource and PageLayoutSource): we can let PageTitleSource be context free, so available everywhere, but we need to constraint the others. ⚠️ Can we define this at UI Patterns level and address this in the current issue?content(in ContentEntitySource): ⚠️ this one is tricky because we need to tell we are in the context of a content entity (so the genericentitycontext is enough) but we are saving the display in a content storage (so entity view overrides). This is important to avoid storing a reference to content from config. How do we express this? See also #3501797: Add a ContentEntity source for slots with a Content entity contextComment #16
pdureau commentedTags are addressed in a dedicated ticket: #3590492: Tidy the source plugins tags
Comment #18
pdureau commentedI have opened a MR and pushed a first commit to help discussion and safely apply the upcoming changes: https://git.drupalcode.org/project/ui_patterns/-/commit/8c8890297163ce59...
With 3 enums:
ContextNames with the IDs of the contexts we will keep because they are correctly typed and are enough for
ContextAwarePluginManagerTraitto work2 are still missing: view display and view rows.
LegacyContextNames with the IDs of the context we will get rid off because too loosely typed are useless to load source plugins (more details in the file):
RequirementsValues to catalog the found values of
context_requirements:It is a bit counter-intuitive to catalog context names like that because:
However, I hope those 3 enums will hep for the work, because they are making the context easier to find in the codebase and we will less rely on "magical strings".
Also, I have added a (still unused and may not be kept at the end) ContextDefinitionsRepository helper to easily test the context structures defined in the issue.
Comment #19
pdureau commentedI may have finally understand the use case of
context_requirementand some specific mechanism we currently have ;)Let's say we have a source selector where the root level contexts are:
entity+ field name, so for example inComponentFormatter. Because I am in the root, I want all sources, , so for a slot:entitycontext: Entity Link, "Entity -> Field"...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:entitycontext: Entity Link, "Entity -> Field"...But i don't want all of them here, I just switched contexts, I want only the ones which differ from the original context:
It works like that today, that's great, we need to keep this.
Comment #20
pdureau commentedHi Mikael,
I have a proposal for the current state of this ticket:
modules/ui_patterns_temp/*src/Plugin/Context/ContextDefinitionsRepository.phpsrc/Plugin/DataType/*Because I believe this work is just tidying up without messing with compatibility and can be deliver ASAP (statement to careful check together):
ui_patterns_views:plugin:display_handler,ui_patterns:field:itemsandui_patterns:lang_codecontexts removalSo, we can start a new ticket with the more impactful changes and more focused MR.
Comment #22
pdureau commentedDone. Removed from MR #501 (to review). Kept in MR # 509 (for future use).
The phpmd pipeline fail is fixed in #3591167: Tidy metadata plugin attributes will be merged before.