Custom Search API processor for paragraphs

Adding new fields to search index is quite straight forward task, but content types can be composed of many different fields. One of them is entity reference field which points to a specific target entity.

The following example will demonstrate the usage of a Search API processor plugin called generic_description which extracts the contents from paragraphs which can be used as an excerpt when showing all available items list (no search term has been entered by the user).

<?php

namespace Drupal\module_name\Plugin\search_api\processor;

use Drupal\Core\Entity\EntityInterface;
use Drupal\search_api\Plugin\search_api\datasource\ContentEntity;
use Drupal\search_api\Processor\ProcessorPluginBase;
use Drupal\search_api\Datasource\DatasourceInterface;
use Drupal\search_api\Processor\ProcessorProperty;
use Drupal\search_api\Item\ItemInterface;

/**
* Extract node paragraphs.
*
* @SearchApiProcessor(
*   id = "generic_description",
*   label = @Translation("Generic description"),
*   description = @Translation("Generate generic item description."),
*   stages = {
*    "add_properties" = 0,
*   },
*   locked = true,
*   hidden = false,
* )
*/
class GenericDescription extends ProcessorPluginBase {

  /**
  * Field name.
  *
  * @var string
  */
  const PROPERTY_ID = 'generic_description';

  /**
  * {@inheritdoc}
  */
  public function getPropertyDefinitions(DatasourceInterface $datasource = NULL) {
    $properties = [];
    /** @var \Drupal\search_api\Plugin\search_api\datasource\ContentEntity $datasource */
    if ($datasource instanceof ContentEntity && $datasource->getEntityTypeId() === 'node') {
      $definition = [
        'label' => $this->t('Generic description'),
        'description' => $this->t('Generate generic item description.'),
        'type' => 'string',
        'processor_id' => $this->getPluginId(),
      ];
      $properties[self::PROPERTY_ID] = new ProcessorProperty($definition);
    }
    return $properties;
  }

  /**
  * {@inheritdoc}
  */
  public function addFieldValues(ItemInterface $item) {
    /** @var \Drupal\Core\Entity\EntityInterface $entity */
    $entity = $item->getOriginalObject()->getValue();
    /** @var \Drupal\search_api\Item\Field $field */
    $field = $item->getField(self::PROPERTY_ID);
    $field->addValue($this->generateDescription($entity));
  }

  /**
  * Generate generic description from entity fields.
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   Entity object.
  * @param int $maxLength
  *   Description string length. Default: 300.
  *
  * @return string
  *   Generic description.
  */
  public function generateDescription(EntityInterface $entity, int $maxLength = 300) : string {
    $description = 'This is description';
    // Generate description from paragraphs.
    if ($entity->hasField('field_paragraphs') && ($paragraphs = $entity->get('field_paragraphs'))) {
      // Fields we are going to extract from paragraphs.
      $extactedFields = ['field_text'] ?? [];
      $paragraphsList = $paragraphs->referencedEntities() ?? [];
      /** @var \Drupal\paragraphs\Entity\Paragraph $paragraph */
      foreach ($paragraphsList as $paragraph) {
        // Stop generating description once string exceeds max length.
        if (strlen($description) >= $maxLength) {
          break;
        }
        foreach ($extactedFields as $fieldName) {
          if ($paragraph->hasField($fieldName)) {
            $description .= mb_substr($paragraph->get($fieldName)->value, 0, $maxLength) . ' ';
          }
        }
      }
    }
    return $description;
  }

}

Annotation described

Search API processor plugin can interact at different stages (after indexing, before query execution, etc.). This example is using only the add_property, since we must be able to add our field to the index.

More information about different processor stages can be found at Search API processor documentation.

We are using locked annotation property which means that the processor will always be enabled for all indexes. You can set it to false and enable it manually if you want at /admin/config/search/search-api/index/node_index/processors.

There is also another option called hidden. If we use the later and set it to true, the processor will not be shown in the UI processors tab.

Property definition

To create our new property we implement ProcessorPluginBase::getPropertyDefinitions method. Based on the datasource provided as an argument we can then decide which datasource we are going to support. In our case we are allowing this property to be added only to the node entity.

And here is the full list of property definitions we have available to work with.

Property definition options

  • is_list: Determines whether the data is multi-valued, i.e. a list of data items.
  • hidden: Determines whether this property should be hidden from the UI.
  • read-only: Determines whether this property is ready-only.
  • required: Determines whether a data value is required.
  • computed: Determines whether the data value is computed (data depended on other values).
  • internal: Determines whether the data value is internal (no need to expose it externally).

Next, we head over to /admin/config/search/search-api/index/node_index/fields to add a newly created field to our desired entity.

Image
Add index custom search field

Add new comment

CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.