Articles


How to create and expose computed properties to the REST API in Drupal 8 &10

How to create and expose computed properties to the REST API in Drupal 8 &10



Posted by admin ,20th May 2024

In Drupal 8.5.0, the REST apps can render the HTML output of a text area without worrying about the filter format as the “processed” property of text field is available in REST.

Guide on integrating computed properties into Drupal 8 & 10 REST API

In this article, we will be exploring how you can add your own processed fields to be output via the REST API.

The computed property on the text area field is known as the “Processed” property, which is mentioned above.

It’s very useful that the computed properties are available for the REST API, for an example, if the user enters a raw input value and Drupal performs some intense logical operations on that before showing the output.

Drupal field entities also have the computed properties and these properties can be explored in REST. The following solution can expose the data of an entity field which takes the raw input data from the users and perform complex calculations on it.

First, you need to write hook_entity_bundle_field_info to add the property and because it is a computed field we don't need to implement hook_entity_field_storage_info.

<?php// my_module/my_module.module/**

* @file

* Module file for my_module.

*/useDrupal\my_module\FieldStorageDefinition;

useDrupal\my_module\Plugin\Field\MyComputedItemList

/**

* Implementshook_entity_bundle_field_info().

*/

functionmy_module_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {

$fields = [];

// Add a property only to nodes of the 'my_bundle' bundle.if ($entity_type->id() === 'node' && $bundle === 'my_bundle') {

// It is not a basefield so we need a custom field storage definition see// https://www.drupal.org/project/drupal/issues/2346347#comment-12206126

$fields['my_computed_property'] = FieldStorageDefinition::create('string')

->setLabel(t('My computed property'))

->setDescription(t('This is my computed property.'))

->setComputed(TRUE)

->setClass(MyComputedItemList::class)

->setReadOnly(FALSE)

->setInternal(FALSE)

->setDisplayOptions('view', [

'label' => 'hidden',

'region' => 'hidden',

'weight' => -5,

])

->setDisplayOptions('form', [

'label' => 'hidden',

'region' => 'hidden',

'weight' => -5,

])

->setTargetEntityTypeId($entity_type->id())

->setTargetBundle($bundle)

->setName('my_computed_property')

->setDisplayConfigurable('form', FALSE)

->setDisplayConfigurable('view', FALSE);

}

return $fields;

}

Then you need the My_Computed_Item_List class to perform some magic. This class will allow us to set the computed field value.

<?php// my_module/src/Plugin/Field/MyComputedItemList.phpnamespaceDrupal\my_module\Plugin\Field;

 

useDrupal\Core\Field\FieldItemList;

useDrupal\Core\TypedData\ComputedItemListTrait;

 

/**

* My computed item list class.

*/classMyComputedItemListextendsFieldItemList{

 

useComputedItemListTrait;

 

/**

* {@inheritdoc}

*/protectedfunctioncomputeValue(){

$entity = $this->getEntity();

if ($entity->getEntityTypeId() !== 'node' || $entity->bundle() !== 'my_bundle' || $entity->my_some_other_field->isEmpty()) {

return;

}

$some_string = some_magic($entity->my_some_other_field);

$this->list[0] = $this->createItem(0, $some_string);

}

The field you added is not a base field so you can't use \Drupal\Core\Field\BaseFieldDefinition. There is an open core issue to address that https://www.drupal.org/project/drupal/issues/2346347 but in tests there is a workaround using a copy of \Drupal\entity_test\FieldStorageDefinition:

<?php// my_module/src/FieldStorageDefinition.phpnamespaceDrupal\my_module;

useDrupal\Core\Field\BaseFieldDefinition;

/**

* A custom field storage definition class.

*

* For convenience we extend from BaseFieldDefinition although this should not

* implement FieldDefinitionInterface.

*

* @todo Provide and make use of a proper FieldStorageDefinition class instead:

* https://www.drupal.org/node/2280639.

*/classFieldStorageDefinitionextendsBaseFieldDefinition{

 

/**

* {@inheritdoc}

*/publicfunctionisBaseField(){

returnFALSE;

}

}

Last but not least you needed to announce your property definition to the entity system so that it can keep track of it. As it is an existing bundle you can write an update hook. Otherwise, you would need to implement hook_entity_bundle_create.

<?php// my_module/my_module.install/**

* @file

* Install file for my module.

*/useDrupal\my_module\FieldStorageDefinition;

useDrupal\my_module\Plugin\Field\MyComputedItemList;

 

/**

* Adds my computed property.

*/functionmy_module_update_8001(){

 

$fields['my_computed_property'] = FieldStorageDefinition::create('string')

->setLabel(t('My computed property'))

->setDescription(t('This is my computed property.'))

->setComputed(TRUE)

->setClass(MyComputedItemList::class)

->setReadOnly(FALSE)

->setInternal(FALSE)

->setDisplayOptions('view', [

'label' => 'hidden',

'region' => 'hidden',

'weight' => -5,

])

->setDisplayOptions('form', [

'label' => 'hidden',

'region' => 'hidden',

'weight' => -5,

])

->setTargetEntityTypeId('node')

->setTargetBundle('my_bundle')

->setName('my_computed_property')

->setDisplayConfigurable('form', FALSE)

->setDisplayConfigurable('view', FALSE);

 

// Notify the storage about the new field.

\Drupal::service('field_definition.listener')->onFieldDefinitionCreate($fields['my_computed_property']);

}

 

In this solution, you won’t need to write a custom serialiser to normalise the output. Drupal Data API will be doing all the heavy lifting for you.