You are here

SolrFieldManager.php in Search API Solr 8.3

Same filename and directory in other branches
  1. 8.2 src/SolrFieldManager.php
  2. 4.x src/SolrFieldManager.php

File

src/SolrFieldManager.php
View source
<?php

namespace Drupal\search_api_solr;

use Drupal\Component\Utility\Unicode;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\UseCacheBackendTrait;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\LoggerTrait;
use Drupal\search_api_solr\TypedData\SolrFieldDefinition;
use Psr\Log\LoggerInterface;

/**
 * Manages the discovery of Solr fields.
 */
class SolrFieldManager implements SolrFieldManagerInterface {
  use UseCacheBackendTrait;
  use StringTranslationTrait;
  use LoggerTrait;

  /**
   * Static cache of field definitions per Solr server.
   *
   * @var array
   */
  protected $fieldDefinitions;

  /**
   * Storage for Search API servers.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $serverStorage;

  /**
   * Constructs a new SorFieldManager.
   *
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
   *   The cache backend.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Psr\Log\LoggerInterface $logger
   *   Logger for Search API.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   */
  public function __construct(CacheBackendInterface $cache_backend, EntityTypeManagerInterface $entityTypeManager, LoggerInterface $logger) {
    $this->cacheBackend = $cache_backend;
    $this->serverStorage = $entityTypeManager
      ->getStorage('search_api_server');
    $this
      ->setLogger($logger);
  }

  /**
   * {@inheritdoc}
   *
   * @throws \Drupal\search_api\SearchApiException
   */
  public function getFieldDefinitions(IndexInterface $index) {

    // We need to prevent the use of the field definition cache when we are
    // about to save changes, or the property check in Index::presave will work
    // with stale cached data and remove newly added property definitions.
    // We take the presence of $index->original as indicator that the config
    // entity is being saved.
    if (!empty($index->original)) {
      return $this
        ->buildFieldDefinitions($index);
    }
    $index_id = $index
      ->id();
    if (!isset($this->fieldDefinitions[$index_id])) {

      // Not prepared, try to load from cache.
      $cid = 'solr_field_definitions:' . $index_id;
      if ($cache = $this
        ->cacheGet($cid)) {
        $field_definitions = $cache->data;
      }
      else {
        $field_definitions = $this
          ->buildFieldDefinitions($index);
        $this
          ->cacheSet($cid, $field_definitions, Cache::PERMANENT, $index
          ->getCacheTagsToInvalidate());
      }
      $this->fieldDefinitions[$index_id] = $field_definitions;
    }
    return $this->fieldDefinitions[$index_id];
  }

  /**
   * Builds the field definitions for a Solr server.
   *
   * Initially the defintions will be built from a the response of a luke query
   * handler directly from Solr. But once added to the Drupal config, the
   * definitions will be a mix of the Drupal config and not yet used fields from
   * Solr. This strategy also covers scenarios when the Solr server is
   * temporarily offline or re-indexed and prevents exceptions in Drupal's admin
   * UI.
   *
   * @param \Drupal\search_api\IndexInterface $index
   *   The index from which we are retrieving field information.
   *
   * @return \Drupal\Core\TypedData\DataDefinitionInterface[]
   *   The array of field definitions for the server, keyed by field name.
   *
   * @throws \InvalidArgumentException
   * @throws \Drupal\search_api\SearchApiException
   */
  protected function buildFieldDefinitions(IndexInterface $index) {
    $solr_fields = $this
      ->buildFieldDefinitionsFromSolr($index);
    $config_fields = $this
      ->buildFieldDefinitionsFromConfig($index);
    $fields = $solr_fields + $config_fields;

    /*** @var \Drupal\Core\TypedData\DataDefinitionInterface $field */
    foreach ($config_fields as $key => $field) {

      // Always use the type as already configured in Drupal previously.
      $fields[$key]
        ->setDataType($field
        ->getDataType());
    }
    return $fields;
  }

  /**
   * Builds the field definitions from exiting index config.
   *
   * @param \Drupal\search_api\IndexInterface $index
   *   The index from which we are retrieving field information.
   *
   * @return \Drupal\Core\TypedData\DataDefinitionInterface[]
   *   The array of field definitions for the server, keyed by field name.
   *
   * @throws \Drupal\search_api\SearchApiException
   */
  protected function buildFieldDefinitionsFromConfig(IndexInterface $index) {
    $fields = [];
    foreach ($index
      ->getFields() as $index_field) {
      $solr_field = $index_field
        ->getPropertyPath();
      $field = new SolrFieldDefinition([
        'schema' => '',
      ]);
      $field
        ->setLabel($index_field
        ->getLabel());
      $field
        ->setDataType($index_field
        ->getType());
      $fields[$solr_field] = $field;
    }
    return $fields;
  }

  /**
   * Builds the field definitions for a Solr server from its Luke handler.
   *
   * @param \Drupal\search_api\IndexInterface $index
   *   The index from which we are retrieving field information.
   *
   * @return \Drupal\Core\TypedData\DataDefinitionInterface[]
   *   The array of field definitions for the server, keyed by field name.
   *
   * @throws \InvalidArgumentException
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   * @throws \Drupal\search_api\SearchApiException
   */
  protected function buildFieldDefinitionsFromSolr(IndexInterface $index) {

    /** @var \Drupal\search_api\ServerInterface|null $server */
    $server = $index
      ->getServerInstance();

    // Load the server entity.
    if ($server === NULL) {
      throw new \InvalidArgumentException('The Search API server could not be loaded.');
    }

    // In case the targeted Solr index may not have fields (yet) we'll return an
    // empty list.
    $fields = [];

    // Don't attempt to connect to server if config is disabled. Cache will
    // clear itself when server config is enabled again.
    if ($server
      ->status()) {
      $backend = $server
        ->getBackend();
      if (!$backend instanceof SolrBackendInterface) {
        throw new \InvalidArgumentException("The Search API server's backend must be an instance of SolrBackendInterface.");
      }
      try {
        $connector = $backend
          ->getSolrConnector();
        if ($connector instanceof SolrCloudConnectorInterface) {
          $connector
            ->setCollectionNameFromEndpoint($backend
            ->getCollectionEndpoint($index));
        }
        $luke = $connector
          ->getLuke();
        foreach ($luke['fields'] as $name => $definition) {
          $field = new SolrFieldDefinition($definition);
          $label = Unicode::ucfirst(trim(str_replace('_', ' ', $name)));
          $field
            ->setLabel($label);

          // The Search API can't deal with arbitrary item types. To make things
          // easier, just use one of those known to the Search API. Using strpos
          // matches point and trie variants as well, for example int, pint and
          // tint. Finally this function only feeds the presets for the config
          // form, so mismatches aren't critical.
          if (strpos($field
            ->getDataType(), 'text') !== FALSE) {
            $field
              ->setDataType('search_api_text');
          }
          elseif (strpos($field
            ->getDataType(), 'date') !== FALSE) {
            $field
              ->setDataType('timestamp');
          }
          elseif (strpos($field
            ->getDataType(), 'int') !== FALSE) {
            $field
              ->setDataType('integer');
          }
          elseif (strpos($field
            ->getDataType(), 'long') !== FALSE) {
            $field
              ->setDataType('integer');
          }
          elseif (strpos($field
            ->getDataType(), 'float') !== FALSE) {
            $field
              ->setDataType('float');
          }
          elseif (strpos($field
            ->getDataType(), 'double') !== FALSE) {
            $field
              ->setDataType('float');
          }
          elseif (strpos($field
            ->getDataType(), 'bool') !== FALSE) {
            $field
              ->setDataType('boolean');
          }
          else {
            $field
              ->setDataType('string');
          }
          $fields[$name] = $field;
        }
      } catch (SearchApiSolrException $e) {
        $this
          ->getLogger()
          ->error('Could not connect to server %server, %message', [
          '%server' => $server
            ->id(),
          '%message' => $e
            ->getMessage(),
        ]);
      }
    }
    return $fields;
  }

}

Classes

Namesort descending Description
SolrFieldManager Manages the discovery of Solr fields.