Create custom fields using a custom processor

Last updated on
8 November 2023

The Search API module allows other modules to provide additional properties that can be indexed by implementing a processor plugin. These properties will then be listed just like the "normal" ones on the search index's "Add fields" form.

The simplest example of such a processor is the "URL field" processor, which adds the "URI" field (which can be found in the "General" datasource section). You can view the latest version of that processor's code here. Please use it as a reference to compare the following sections.

Processor plugin definition

Processors are defined, as most plugin types in Drupal 8, via an annotation in the class comment:

/**
 * @SearchApiProcessor(
 *   id = "add_url",
 *   label = @Translation("URL field"),
 *   description = @Translation("Adds the item's URL to the indexed data."),
 *   stages = {
 *     "add_properties" = 0,
 *   },
 *   locked = true,
 *   hidden = true,
 * )
 */

ID, label and description can be freely picked for this processor, as usual. Please just prefix the ID with your module's name (especially if you plan to publish it) to avoid the potential of conflicts.

As the only stage (unless your processor should do more than provide properties) please use "add_properties", with an arbitrary weight.

For a list of all available stages and additional explanations check out the Drupal\search_api\Processor\ProcessorInterface.php.

The last two lines set the processor to be locked and hidden, so that it isn't displayed in the user interface (which means the label and description selected above aren't really displayed anywhere) and is just always enabled. This is done since it won't have any effect as long as none of the provided properties is actually added to the index, but it makes the process of doing so much easier (one step instead of two). If you prefer, you can also skip those two lines to be able to enable or disable the processor in the user interface.

// hidden = true -> Checkbox won't be shown on Processor's Tab.
// locked = true -> If it is not hidden, the checkbox will be shown as "disabled". Note that locked means that is is always enabled (for all indexes).

Defining the custom properties

The getPropertyDefinitions() method defines which properties the processor provides that can be added to an index. As soon as you return a property from this method (since it's a code change, you might also need to manually clear the cache), it will appear on the index's "Add fields" form.

In our example:

  public function getPropertyDefinitions(DatasourceInterface $datasource = NULL) {
    $properties = [];

    if (!$datasource) {
      $definition = [
        'label' => $this->t('URI'),
        'description' => $this->t('A URI where the item can be accessed'),
        'type' => 'string',
        'is_list' => FALSE,
        'processor_id' => $this->getPluginId(),
      ];
      $properties['search_api_url'] = new ProcessorProperty($definition);
    }

    return $properties;
  }

This method will be called when getting available properties, once for every datasource (and once for datasource-independent properties, with NULL). Since the "URI" property should be datasource-independent (i.e., listed under "General" and active for items of all datasources), we only return the property for the NULL call. For that case, we create a new Typed Data property definition and return it in the search_api_url key of the returned array. Returning a property definition that implements ProcessorPropertyInterface and correctly defines the plugin ID of the defining processor ensures that our processor's code will be called when retrieving a value for the property.

Computing property values at indexing time

Once the user adds our custom property to the index as a field, values for this field will have to be provided at indexing time (and, potentially, also under other circumstances, like when displaying the field in a Views result). For this, the addFieldValues()method is used, which is called for the processor defining the field's property. For the "URL field" processor, this method looks as follows:

  public function addFieldValues(ItemInterface $item) {
    $url = $item->getDatasource()->getItemUrl($item->getOriginalObject());
    if ($url) {
      $fields = $this->getFieldsHelper()
        ->filterForPropertyPath($item->getFields(), NULL, 'search_api_url');
      foreach ($fields as $field) {
        $field->addValue($url->toString());
      }
    }
  }

As you can see, the method gets a whole item object as the argument, for which the field values should be added. This means, this method will only be called once for each item, even if the processor defines multiple properties, or multiple fields were added for that property.

To make the latter case more performant (though it's probably very rare), we first compute the field's value for this item (which will of course differ for your custom processor). Only if we actually have a value, we retrieve the item's fields that are based on our property (using the Search API fields helper service's filterForPropertyPath() method) and then simply add the field value(s) to those fields. As the method documentation explains, you should always use addValue() here, and not setValues(), to ensure the values are properly processed by the field's data type plugin.

Configurable properties

By returning an instance of ConfigurablePropertyInterface in getPropertyDefinitions(), you can also define properties which will offer per-field configuration. See the "Rendered item" processor for an example of that.

Tags

Help improve this page

Page status: No known problems

You can: