"Generic" Field Source Plugin Deriver

Created on 20 March 2025, 14 days ago

Problem/Motivation

Right now every FieldSource needs an own deriver which creates the plugin for the related field.
For example the new ui_patterns_source Source needs an extra UIPatternsSourceFieldPropertySourceDeriver

/**
 * Plugin implementation of the prop source.
 */
#[Source(
  id: 'ui_patterns_source',
  label: new TranslatableMarkup('Prop/Slot value from UI Patterns source field'),
  description: new TranslatableMarkup('Prop/Slot value from UI Patterns source field when of type component.'),
  deriver: UIPatternsSourceFieldPropertySourceDeriver::class
)]

Proposed resolution

Create a Generic Deriver for Fields and for FieldProps that can be used by Sources without implementing an own Deriver

/**
 * Plugin implementation of the prop source.
 */
#[Source(
  id: 'ui_patterns_source',
  label: new TranslatableMarkup('Prop/Slot value from UI Patterns source field'),
  description: new TranslatableMarkup('Prop/Slot value from UI Patterns source field when of type component.'),
  deriver: FieldTypeDeriver::class
  field_type: 'ui_patterns_source'
)]

Or for a field_type and prop:

/**
 * Plugin implementation of the prop source.
 */
#[Source(
  id: 'ui_patterns_source',
  label: new TranslatableMarkup('Prop/Slot value from UI Patterns source field'),
  description: new TranslatableMarkup('Prop/Slot value from UI Patterns source field when of type component.'),
  deriver: FieldTypeDeriver::class
  field_type: 'ui_patterns_source'
  field_prop: 'source'
)]

Or for a prop_type only:

/**
 * Plugin implementation of field datatype.
 */
#[Source(
  id: 'ui_patterns_source',
  label: new TranslatableMarkup('Prop/Slot value from UI Patterns source field'),
  description: new TranslatableMarkup('Prop/Slot value from UI Patterns source field when of type component.'),
  deriver: FieldTypeDeriver::class
  field_datatype: Source::class
)]

This would makes it much easier for contributers to build own field related Sources

Feature request
Status

Active

Version

2.0

Component

Code

Created by

🇩🇪Germany Christian.wiedemann

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @Christian.wiedemann
  • Merge request !355Add Generic entity field → (Open) created by Christian.wiedemann
  • 🇫🇷France pdureau Paris

    Hi Christian,

    Thanks for this issue. I hope I am understand what you are trying to do achieve here. I not, I will move this to its own ticket.

    They are 3 levels in Field API and 3 kinds of prop types:

    Indeed, we use typed_data property only with scalar prop types nowadays:

     $ grep -r typed_data src/Plugin/UiPatterns/PropType
    src/Plugin/UiPatterns/PropType/UrlPropType.php:  typed_data: ['uri']
    src/Plugin/UiPatterns/PropType/StringPropType.php:  typed_data: ['string']
    src/Plugin/UiPatterns/PropType/NumberPropType.php:  typed_data: ['decimal', 'float', 'integer']
    src/Plugin/UiPatterns/PropType/EnumPropType.php:  typed_data: ['float', 'integer', 'string'],
    src/Plugin/UiPatterns/PropType/BooleanPropType.php:  typed_data: ['boolean']
    

    Let's focus on the 3 red crosses (❌) instead

    So we have much to do to cover the full field API.

    Every component prop type has a (JSON) schema, every prop type has a (Typed data) schema. They may be matchable.

    Example: Icon field with an Icon prop

    Icon field:

      public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition): array {
        $properties = [];
        $properties['target_id'] = DataDefinition::create('string')
          ->setLabel(new TranslatableMarkup('Icon ID'))
          ->setRequired(TRUE);
        return $properties;
      }
    

    is converted to JSON schema:

    type: object
    properties:
      target_id:
        type: string
    

    Icon prop type:

    type: object
    properties:
      pack_id:
        "$ref": ui-patterns://identifier
      icon_id:
        type: string
      settings:
        type: object
    required:
    - pack_id
    - icon_id
    

    Not easy to map because it seems icon field has a single property (target_id) for 2 prop types properties (pack_id and icon_id). Maybe with a separator. Let's check that with UI Icon team.

    Example: LinkItem field to Links prop

    LinkItem field type :

       public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
            $properties['uri'] = DataDefinition::create('uri')->setLabel(new TranslatableMarkup('URI'));
            $properties['title'] = DataDefinition::create('string')->setLabel(new TranslatableMarkup('Link text'));
            $properties['options'] = MapDataDefinition::create()->setLabel(new TranslatableMarkup('Options'));
            return $properties;
        }

    is converted to JSON schema:

    type: object
    properties:
      title:
        type: string
      uri:
        "$ref": ui-patterns://url
      options:
        type: object
    

    Links prop type:

    type: array
    items:
      type: object
      properties:
        title:
          type: string
        url:
          "$ref": ui-patterns://url
        attributes:
          "$ref": ui-patterns://attributes
        link_attributes:
          "$ref": ui-patterns://attributes
        below:
          type: array
          items:
            type: object
    

    Not bad :) But url and uri property names don't match, so we can have 2 strategies:

    • We don't care about the property names ("title", "uri", "options"), we care only about the type and we let the builder do the mapping.
    • Can we use a PropTypeAdapter plugin transforming uri and url for that? They were not made for this kind of flow, but it may work.

    Example: Multivalued StringItem field to List prop

    StringItem field:

      public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
        $properties['value'] = DataDefinition::create('string')
          ->setLabel(new TranslatableMarkup('Text value'))
          ->setSetting('case_sensitive', $field_definition->getSetting('case_sensitive'))
          ->setRequired(TRUE);
        return $properties;
      }

    Is converted to this JSON schema:

    type: object
    properties:
      value:
        type: string
    

    So this when multivalued:

    type: array
    items:
      type: object
      properties:
        value:
          type: string
    

    List prop type:

    type: array
    items:
        type: ['string', 'number', 'integer']
    

    It doesn't seem obvious at first, but it may be good match if we flat the single "value" field property.

    Conclusion

    None of my examples were a perfect match. But each of them is showing mechanism worth to study. There is something smart to do here.

  • Pipeline finished with Failed
    13 days ago
    Total: 498s
    #453347
  • 🇩🇪Germany Christian.wiedemann

    So to handle icons the ui icon module needs three sources. One for pack_id, icon_id and one for settings.

    /**
     * Plugin implementation of the source_provider.
     */
    #[Source(
      id: 'icon_pack_id',
      label: new TranslatableMarkup('Foo Field(ui_patterns_test)'),
      description: new TranslatableMarkup('Foo description.'),
      prop_types: ['string'],
      deriver: CustomEntityPropertySourceDeriver::class,
      metadata:
      [
        'field_type' => ['icon'],
       'field_property' => ['target_id']
      ],
    )]
    final class IconPackId extends SourcePluginBase {
    DO MAGIC TARGET EXPLODE HERE
    
  • 🇩🇪Germany Christian.wiedemann

    So no magic just not writing a deriver

Production build 0.71.5 2024