Problem/Motivation

Before working on #3540247: Map source contexts with enums which is kind of risky, we need to make sure the mechanisms are understood and documented. We have already done #3590492: Tidy the source plugins tags, it is time to do the metadata attribute for Source and DerivableContexts plugins.

This plugin attribute is an under documented, loosely types associative array and feels a bit "magical".

Proposed resolution

  1. Convert metadata array to "Data Transfer Objects", PHP classes with strong typing and disctinction between optional and mandtory properties.
  2. Remove the unused properties
  3. Document as much as possible the remaining properties.
Command icon 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

pdureau created an issue. See original summary.

pdureau’s picture

Title: Tidy plugin metadata » Tidy metadata plugin attributes

pdureau’s picture

Assigned: pdureau » just_like_good_vibes
Status: Active » Needs work

Works have started with #3590492: Tidy the source plugins tags as a starting point.

Mikael, can you help us document the properties of:

When finished, let's leave it in RTBC without merging, and discuss.

Maybe the change will break ecosystem (like ui_icons_patterns) and we will keep it for 2.1.0. Maybe it will be OK (because are DTO have ArrayAccess for example) and we can merge in 2.0.x

pdureau’s picture

Maybe the change will break ecosystem (like ui_icons_patterns) and we will keep it for 2.1.0. Maybe it will be OK (because are DTO have ArrayAccess for example) and we can merge in 2.0.x

Working on this subject.

pdureau’s picture

Assigned: pdureau » just_like_good_vibes
Status: Needs work » Needs review

Error: Cannot use object of type Drupal\ui_patterns\FieldMetadata as array in Drupal\ui_icons_patterns\Plugin\Derivative\FieldIconSourceDeriver->getDerivativeDefinitionsForEntityStorageFieldProperty() (line 43 of ui_icons/modules/ui_icons_patterns/src/Plugin/Derivative/FieldIconSourceDeriver.php).

Fixed by introducing a (temporary?) ArrayAccessTrait to implements <code>ArrayAccess interface on DTOs.

#3590426: Reduce the API scope of our plugin managers and #3590492: Tidy the source plugins tags must be merged first.

pdureau’s picture

There is also SourceInterface::getCustomPluginMetadata() methods, called only twice:

  • $field_name = $this->getCustomPluginMetadata('field_name'); in FieldValueSourceBase
  • $property = $this->getCustomPluginMetadata('property'); in FieldPropertySource

They belong to SourceMetadata I guess, it was forbidden when annotating the properties with comments.

Does that mean they are "special" and something specific must be done?

pdureau’s picture

Assigned: just_like_good_vibes » pdureau
Status: Needs review » Needs work

Let's investigate this before sending to review:

Warning: Undefined property: Drupal\ui_patterns\SourceMetadata::$group
pdureau’s picture

Assigned: pdureau » just_like_good_vibes
Status: Needs work » Needs review

Let's talk

pdureau’s picture

This is the 2nd part of a three steps effort to prepare UI Patterns Next while staying in 2.0.x branch for now: https://docs.google.com/presentation/d/11NsN1c5prlWrB0AlEKOxE4q5MOFHx-N_...

#3590492: Tidy the source plugins tags must be merged first.

Added DTO classes implementing ArrayAccess:

  • DerivableContextMetadata
  • FieldMetadata
  • SourceMetadata

Removed metadata:

  • FieldMetadata::configurable
  • FieldMetadata::editorial
  • FieldMetadata::parent_base
  • FieldMetadata::base
  • DerivableContextMetadata::group
pdureau’s picture

Assigned: just_like_good_vibes » pdureau
Status: Needs review » Needs work

Oopsy. The change from array to DTO is breaking Display Builder and probably other modules from ecosytem:

TypeError: Drupal\ui_patterns\Attribute\Source::__construct(): Argument #9 ($metadata) must be of type ?Drupal\ui_patterns\SourceMetadata, array given, called in modules/display_builder_entity_view/src/Plugin/UiPatterns/Source/ExtraFieldSource.php on line 22 in src/Attribute/Source.php 

Because we target 2.0.x branch, we need to add a compatibility layer.

pdureau’s picture

Assigned: pdureau » just_like_good_vibes
Status: Needs work » Needs review

Display Builder compatibility fixed:

  • partially in the current MR with the addition of a remporary safeguard:
         public readonly ?array $context_requirements = [],
    -    public readonly ?SourceMetadata $metadata = NULL,
    +    public mixed $metadata = NULL,
         public readonly ?array $context_definitions = [],
    -  ) {}
    +  ) {
    +    if (is_array($this->metadata) && !empty($this->metadata)) 
    +      $this->metadata = new SourceMetadata(...$this->metadata);
    +    }
    +
    +  }
    
  • partially in Dispaly Builder: #3608406: Remove metadata attribute from ExtraFieldSource

Adding a DTO is more risky than adding an enum, we may break stuff. Let's be very careful before merging it.

smovs made their first commit to this issue’s fork.

smovs’s picture

Hi @pdureau and @just_like_good_vibes!

I used Claude to double-check for edge cases, and it identified two potential concerns.

I added a NULL guard to getChoices() and SourcePluginBase.

There are also concerns about the metadata annotation using the wrong entity type. We currently use DerivableContextMetadata, while Claude suggested that source plugins should use SourceMetadata instead.

Claude also suggested several more invasive code changes, but I didn’t implement them without discussing them first.

Concerns identified by Claude:
1. getChoices() — unguarded NULL->provider (DerivableContextSourceBase.php:503-507)

  $metadata = $derivable_context['metadata'];   // was: ?? []
  'provider' => $metadata->provider,            // was: $metadata['provider'] ?? NULL

The DerivableContext attribute now defaults metadata to NULL. Any derivable-context plugin that doesn't route through EntityFieldSourceDeriverBase (i.e. any contrib one) has metadata === NULL → fatal.
Core's two plugins both inherit a SourceMetadata, which is the only reason it's green.

2. getCustomPluginMetadata() — unguarded property access (SourcePluginBase.php:397-403)
return $plugin_definition['metadata']->{$key}; // was: guarded, returned NULL on miss
Fatals if metadata is NULL (any non-field Source plugin) or a key is absent. Safe today only because the two callers are field sources that always have metadata — but this is a general protected helper any source subclass can call.

just_like_good_vibes made their first commit to this issue’s fork.

just_like_good_vibes’s picture

Thanks Pierre, your work surfaced many improvements to do. Thanks smovs too :)

I pushed another proposal on a separate branch, where metadata stays a plain array: 3591167-bc-harness-array-metadata (MR !537).

As we discussed, this work has several objectives:
1. Derivation backward compatibility: derived plugins must keep working across future refactors, same IDs, working plugins, correct data, proven by tests.
2. simplify and trim metadata: keep only what is strictly necessary in derived plugin definitions, less code, no per-plugin-type diversity.
3. Metadata key discipline: array preferred over object, and the remaining keys named once, not hardcoded everywhere.
4. Smaller API surface if possible

What I pushed:
- Tests first: a recorded fixture of the derived plugin IDs (entity references had no coverage at all), assertions on contexts, tags and metadata values, and a check that a stored source_id still resolves.
- Removed the metadata keys: entity_type_id, entity_bundle, field_formatter, the classification flags.
- SourceMetadataKey string enum as the single source of the key names, with a protected getMetadata() accessor. About #7: field_name and property are not special, just the only keys read at runtime.
- getCustomPluginMetadata() removed

Impact on code: -34 useful lines of codes, 2 public methods removed, more tests, 1 file added (SourceMetadataKey)

Needs review.

pdureau’s picture

The DTO proposal was two risky for a commit targeting 2.0.x, so the new proposal is warmly welcomed. Lets' check the scope of both:

MR 504 MR 537
Source metadata As DTO As an enum
Field metadata As DTO Kept?
Derivable context metadata As DTO Kept?
Removed
  • FieldMetadata::configurable
  • FieldMetadata::editorial
  • FieldMetadata::parent_base
  • FieldMetadata::base
  • DerivableContextMetadata::group
  • SourceMetadataKey::entity_type_id
  • SourceMetadataKey::entity_bundle
  • SourceMetadataKey::field_formatter
  • the classification flags

How come we have so different scopes? Can we merge the 2 scopes ? Keeping:

  • From MR 504: the cataloging of Field metadata and derivable context metadata, and the removal of some keys
  • From MR 537: the enum strategy, and the removal of the SourceMetadataKey keys

Or is it too ambitious/risky?

By the way, what are the classification flags?

just_like_good_vibes’s picture

Hello @pdureau,
sorry i realize my previous comment was probably too short about what !537 actually changes, so let me describe it better.

The metadata stays a plain array, same place, same nesting. Only its content shrinks:

Before:

'metadata' => [
  'field' => ['type', 'configurable', 'editorial', 'parent_base', 'base', 'cardinality'],
  'field_name' => ..., 'property' => ..., 'entity_type_id' => ..., 'entity_bundle' => ..., 'provider' => ...,
] + 'field_formatter' => TRUE merged by FieldFormatterSourceDeriver.

After:

'metadata' => [
  'field' => ['type', 'cardinality'],
  'field_name' => ..., 'property' => ..., 'provider' => ...,
]

Every removed key has zero readers, in ui_patterns (and none found in drupal ecosystem).
The enum does not replace the array: SourceMetadataKey only names the keys, once, and getMetadata() reads them.

About your table:
- The classification flags are configurable, editorial, parent_base and base: the same four FieldMetadata flags as in !504 , computed by getEntityFieldsClassification() (removed in the MR) and read by nobody. So the removals of !537 are a superset of the removals of !504 .
- Field metadata: kept, trimmed to type and cardinality, the only keys read at runtime.
- The DerivableContextMetadata class only wrapped a value parsed from the plugin ID at runtime, and !537 renames that method parsePluginId() to say what it really does.

So I believe the merge you describe is what !537 already contains: the DTO classes are the only part of !504 left out, and they are the risky part for 2.0.x.

pdureau’s picture

Assigned: pdureau » just_like_good_vibes
Status: Needs review » Reviewed & tested by the community

Field metadata: kept, trimmed to type and cardinality, the only keys read at runtime.

Indeed, i missed them (and derivable context's) because they were not expressed as enum.

MR 504 MR 537
Source metadata As DTO
Removed: none
As an enum
Removed:
  • entity_type_id
  • entity_bundle
  • field_formatter
Field metadata As DTO
Removed:
  • configurable
  • editorial
  • parent_base
  • base
Kept as strings. Removed:
  • configurable
  • editorial
  • parent_base
  • base
Derivable context metadata As DTO
Removed:
  • group
Replaced by ::parsePluginId()

So RTBC if my understanding (expressed in the table) is right and you don't want to move field metadata keys to a PHP enum.

just_like_good_vibes’s picture

Assigned: just_like_good_vibes » Unassigned
Status: Reviewed & tested by the community » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.