1

I’m working on a custom field (Drupal 8) where the author selects a taxonomy term from defined list, but the display is of, or includes, a node that references said term. I already have a service that can retrieve the desired node based on the term id. I started with a custom FieldFormatter for entity references, and that does a reasonable job of displaying the node information as desired.

However, what I really need is to get that node information included in a jsonapi response. I’ve made a custom FieldType extending EntityReferenceItem figuring all I would need is to add a computed property value (See https://www.drupal.org/node/2112677, e.g.). The examples keep pointing to the use of a specialized class extending TypedData, but the EntityReferenceItem treats the main property as computed, and doesn’t use that.

I also read How to properly define and return simple array computed field property but I'm not sure it's addressing the same kind of thing nor is there quite enough detail there to help me implement a solution.

class MyField extends EntityReferenceItem { ...
    public function getValue() {
        $values = parent::getValue();
        $values['computed'] = 'foobar';
        return $values;
    }
}

my_module/config/schema/my_module.data_types.schema.yml -

# Schema copied and modified from the configuration of the Entity reference field type
# in docroot/core/config/schema/core.data_types.schema.yml
field.storage_settings.my_field:
  type: mapping
  label: 'My field storage settings'
  mapping:
    target_type:
      type: string
      label: 'Blah blah blah'
field.field_settings.my_field:
  type: mapping
  label: 'Another label'
  mapping:
    handler:
      type: string
      label: 'Reference method'
    handler_settings:
      type: entity_reference_selection.[%parent.handler]
      label: 'Yet another label'
field.value.my_field:
  type: mapping
  label: 'Default value'
  mapping:
    target_id:
      type: string
      label: 'Value'
    target_uuid:
      type: uuid
# This part is new:
field.computed.my_field:
  type: mapping
  label: 'Hello World'
  mapping:
    target_id:
      type: string
      label: 'Computed Value'
    target_uuid:
      type: uuid

Any pointers out there?

My understanding is that I can set things up so that the computed value shows up in the $items parameter passed into my custom Formatter's viewElements() method, and, at least as important, will be part of a JSONAPI response when my_field is part of the include query parameter. As of now, neither of those things occurs.

0

1 Answer 1

0

UDPATE

I think the new part of your configuration is not necessary. I recall that there is a module called entity_reference_uuid which does something like you want: extends from core ER field type, adds an extra column target_uuid and uses the entity property which is computed from the reference.

In their schema they only have these keys definition:

  • field.storage_settings.entity_reference_uuid
  • field.field_settings.entity_reference_uuid
  • field.value.entity_reference_uuid

If you look here you will see the entity is computed (like the one you want to add). Further down you can see they kept sync between the entity property during ::setValue ::getValue methods and implement ::onChange to notify any changes on the property. This all are entity lifecycle logic, nothing to do with rendering.

Until here everything is more like your code.

Later on the formmatter they just iterate over the referenced entities which are obtained from the $items. The thing is I think that your problem comes from not implementing the \Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase::prepareView:

/**
 * {@inheritdoc}
 *
 * Loads the entities referenced in that field across all the entities being
 * viewed.
 */
public function prepareView(array $entities_items) {
  ...
  // Fragment fromt line 142 and on.
  // For each item, pre-populate the loaded entity in $item->entity, and set
  // the 'loaded' flag.
  foreach ($entities_items as $items) {
    foreach ($items as $item) {
      if (isset($target_entities[$item->target_id])) {
        $item->entity = $target_entities[$item->target_id];
        $item->_loaded = TRUE;
      }
      elseif ($item->hasNewEntity()) {
        $item->_loaded = TRUE;
      }
    }
  }
}

As you can see there is where the entity key is populated for the formatter to use.


ORIGINAL ANSWER

By default all (most) pieces of data in Drupal 8 are typed data or get composed by it; means that fields are complex typed data which are like maps or mappings. EntityReferenceItem extends from \Drupal\Core\Field\FieldItemBase which extends from \Drupal\Core\TypedData\Plugin\DataType\Map. As explained in both of your renferences you should override the ::getValue method from EntityReferenceItem. Here is an example

/**
 * {@inheritdoc}
 */
public function getValue() {
  $values = parent::getValue();
  $values['computed_value'] = $this->myAwesomeService->getJSONApiData();      
  return $values;
}

There on your formatters (and view's field) can use the computed_value key from your field.

6
  • An override of getValue() just like that is all I have right now in my custom FieldType, it's the only thing in my class. However, I do not see that computed item in either a JSONAPI request that includes the field, nor in the formatter's viewElements() by inspecting the variables passed in. (I expected/hoped it would be in the $items parameter.) Is there something else I need?
    – beltouche
    Feb 19, 2020 at 0:08
  • 1
    Oh, I see. Did you define the schema.yml for your field? I mean one thing is the schema method in your field type which is used for the storage layer, but also you need to tell the rest of Drupal how your field is composed. Check core/modules/field/config/schema/field.schema.yml, this schema holds the information from all core fields, this mapping tells other components like formatters how your field value works.
    – d70rr3s
    Feb 19, 2020 at 0:23
  • Also your probably figure it out already but, take in mind that when you get a field like $entity->get('my_field') what you got there is a list of your field item class, I mean \Drupal\Core\Field\EntityReferenceFieldItemList is the actual field value for an entity reference field, where each list element is a \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem.
    – d70rr3s
    Feb 19, 2020 at 0:26
  • No. I hadn't created a schema.yml file. I thought that was only for establishing the database schema, and since I'm piggybacking off an existing field, only adding a computed value. I'll have to look at that tomorrow. I'm sure I'll be able to parse through the info once get it to show up in the formatter. Right now, even a dummy $values['computed_value'] = 'foobar' isn't visible.
    – beltouche
    Feb 19, 2020 at 1:12
  • Everytime you create or implement a plugin type you are responsible for defining a schema for that plugin if it's resulting configuration is not defined by parent or base. Take in consideration for example formatters, all (most) formatters extend from the base plugin class which also has a base configuration schema in field.schema.yml but thei leverage the hierarchical capabilities of the configuration schema and delegate the configuration using placeholders. Check this cheatsheet about schema definition hojtsy.hu/files/ConfigSchemaCheatSheet1.5.pdf
    – d70rr3s
    Feb 19, 2020 at 8:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.