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 any context and using entity only 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:
proposal

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:

  • ContextAwarePluginInterface like the other context aware source plugins
  • ContextProviderInterface like 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 entity context: 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 entity context: 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

pdureau created an issue.