Skip to main content

SEARCH API - Create custom fields using custom Processors in Drupal 8

Create custom fields using custom Processors in Drupal 8

Summary

This is a Drupal 8 example.

Using the Search API module will allow you to have a fast and complete search feature solution in your site, but sometimes there are some scenarios where we want to modify the behaviour of the indexed items.

Scenario

We are using Search API module with the Search API Solr backend.

Let's say we have a content type called "Expedient". This content type has a (entity reference) field pointing to a custom entity with some metadatas that we want to index and search.
Probably there will be many solutions for this scenario, but we will use a custom Processor to achieve this goal.

Example

In this example we will create a search api Plugin processor taking the idea from these existing ones: AddURL and AggregatedFields processors.

Basically we will follow 3 steps:

  1. alterPropertyDefinitions: We will define (create) three custom fields of different types.
  2. preprocessIndexItems: We will use these fields depending on some "custom" conditions.
  3. preIndexSave: We will let the index know about these fields and use them as if they were a "normal" content type field (just like the nid, for example)

Code

Updates and code available in this snippet and the Drupal Community Documentation.

1. alterPropertyDefinitions()

We will define our fields with some label, description and type. We need to define them if we want to use them later on.

<?php

/**
 * @file
 * Contains \Drupal\MY_MODULE\Plugin\search_api\processor\AddCustomField.
 *
 * A proccesor example that adds some custom fields.
 * 
 * @NOTE 
 *   - Replace any occurrences of MY_MODULE with your module machine_name.
 *   - Name this file AddCustomField.php and place it in the correct folder.
 *
 * @TODO Make some tests.
 */

namespace Drupal\MY_MODULE\Plugin\search_api\processor;

use Drupal\Core\TypedData\DataDefinition;
use Drupal\search_api\Datasource\DatasourceInterface;
use Drupal\search_api\Processor\ProcessorPluginBase;


/**
 * @SearchApiProcessor(
 *   id = "MY_MODULE_add_custom_field",
 *   label = @Translation("Custom field examples"),
 *   description = @Translation("Adds custom field examples."),
 *   stages = {
 *     "pre_index_save" = -10,
 *     "preprocess_index" = -30
 *   }
 * )
 */
class AddCustomField extends ProcessorPluginBase {
  /**
   * {@inheritdoc}
   *
   */
  public function alterPropertyDefinitions(array &$properties, DatasourceInterface $datasource = NULL) {
    // These fields will not have a Data source associated to them.
    if ($datasource) {
      return;
    }

    // Ensure that our fields are defined.
    $fields = $this->getFieldsDefinition();

    foreach ($fields as $field_id => $field_definition) {
      $properties[$field_id] = new DataDefinition($field_definition);
    }
  }

  /**
   * Helper function for defining our custom fields.
   */
  protected function getFieldsDefinition() {
    $fields['MY_MODULE_field_based_on_nid'] = array(
      'label' => 'Custom field based on nid',
      'description' => 'I will be used to make a text type field with an id if "nid" field exists.',
      'type' => 'text',
      'prefix' => 't', // For Solr fields I think it is ok.
    );
    $fields['MY_MODULE_field_active_standby'] = array(
      'label' => 'Custom field always added, could be used for facets',
      'description' => 'Custom field 2 of type string will be always added with values active/standby.',
      'type' => 'string',
      'prefix' => 's', // For Solr fields I think it is ok.
    );
    $fields['MY_MODULE_field_date_experiment'] = array(
      'label' => 'Custom field of date type, always added',
      'description' => 'I will be added as a time(), but it will be indexed differently.',
      'type' => 'date',
      'prefix' => 'd', // For Solr fields I think it is ok.
    );

    return $fields;
  }
}

2. preprocessIndexItems()

Here we add our business logic and set our conditions for indexing the fields and values.

<?php
  /**
   * {@inheritdoc}
   */
  public function preprocessIndexItems(array &$items) {

    foreach ($items as $item) {

      // First custom field will only be added if we are indexing "nid" field.

      // Note that here we could check a "field_reference" (instead of the nid) to get an Id, pull data from that referenced entity
      // and add the proper values.
      foreach ($this->filterForPropertyPath($item->getFields(), 'nid') as $field_reference) {
        foreach ($this->filterForPropertyPath($item->getFields(), 'MY_MODULE_field_based_on_nid') as $nid_based_field) {
          $nid_based_field->addValue('Node Id: ' . reset($field_reference->getValues()));
        }
      }

      // We will add always this custom field, with random value.
      foreach ($this->filterForPropertyPath($item->getFields(), 'MY_MODULE_field_active_standby') as $always_added_field) {
        $active_standby = rand(0,1) ? 'active' : 'standby';

        $always_added_field->addValue($active_standby);
      }

      // We will add a time() and see how it is indexed.
      foreach ($this->filterForPropertyPath($item->getFields(), 'MY_MODULE_field_date_experiment') as $date_experiment_field) {
        $date_experiment_field->addValue(time());
      }
    }
  }

3. preIndexSave()

<?php
  /**
   * {@inheritdoc}
   *
   */
  public function preIndexSave() {
    foreach ($this->getFieldsDefinition() as $field_id => $field_definition) {
      // We can specify or not a type in the 3rd parameter.
      // If you don't specify the type, the user will be able to change it through
      // the user interface (could be very useful), but if you specify it, the user won't
      // be able to alter the type.
      $this->ensureField(NULL, $field_id, $field_definition['type']);
    }
  }

"Magically" these fields will be available on the UI when we ensure them.

Answers are generated automatically by AI and may not be accurate.