You are here

class Item in Search API 8

Provides a default implementation for a search item.

Hierarchy

Expanded class hierarchy of Item

4 files declare their use of Item
FieldsHelper.php in src/Utility/FieldsHelper.php
SerializationTest.php in tests/src/Kernel/System/SerializationTest.php
TestItemsTrait.php in tests/src/Unit/Processor/TestItemsTrait.php
ViewsFieldTraitTest.php in tests/src/Kernel/Views/ViewsFieldTraitTest.php
1 string reference to 'Item'
DatasourceTaskTest::setUp in tests/src/Kernel/Datasource/DatasourceTaskTest.php

File

src/Item/Item.php, line 19

Namespace

Drupal\search_api\Item
View source
class Item implements \IteratorAggregate, ItemInterface {
  use LoggerTrait;

  /**
   * The search index with which this item is associated.
   *
   * @var \Drupal\search_api\IndexInterface
   */
  protected $index;

  /**
   * The ID of the index with which this item is associated.
   *
   * This is only used to avoid serialization of the index in __sleep() and
   * __wakeup().
   *
   * @var string
   */
  protected $indexId;

  /**
   * The ID of this item.
   *
   * @var string
   */
  protected $itemId;

  /**
   * The complex data item this Search API item is based on.
   *
   * @var \Drupal\Core\TypedData\ComplexDataInterface
   */
  protected $originalObject;

  /**
   * The ID of this item's datasource.
   *
   * @var string
   */
  protected $datasourceId;

  /**
   * The datasource of this item.
   *
   * @var \Drupal\search_api\Datasource\DatasourceInterface
   */
  protected $datasource;

  /**
   * The language code of this item.
   *
   * @var string
   */
  protected $language;

  /**
   * The extracted fields of this item.
   *
   * @var \Drupal\search_api\Item\FieldInterface[]
   */
  protected $fields = [];

  /**
   * Whether the fields were already extracted for this item.
   *
   * @var bool
   */
  protected $fieldsExtracted = FALSE;

  /**
   * The HTML text with highlighted text-parts that match the query.
   *
   * @var string
   */
  protected $excerpt;

  /**
   * The score this item had as a result in a corresponding search query.
   *
   * @var float
   */
  protected $score = 1.0;

  /**
   * The boost of this item at indexing time.
   *
   * @var float
   */
  protected $boost = 1.0;

  /**
   * Extra data set on this item.
   *
   * @var array
   */
  protected $extraData = [];

  /**
   * Cached access results for the item, keyed by user ID.
   *
   * @var \Drupal\Core\Access\AccessResultInterface[]
   *
   * @see getAccessResult()
   */
  protected $accessResults = [];

  /**
   * Constructs an Item object.
   *
   * @param \Drupal\search_api\IndexInterface $index
   *   The item's search index.
   * @param string $id
   *   The ID of this item.
   * @param \Drupal\search_api\Datasource\DatasourceInterface|null $datasource
   *   (optional) The datasource of this item. If not set, it will be determined
   *   from the ID and loaded from the index.
   */
  public function __construct(IndexInterface $index, $id, DatasourceInterface $datasource = NULL) {
    $this->index = $index;
    $this->itemId = $id;
    if ($datasource) {
      $this->datasource = $datasource;
      $this->datasourceId = $datasource
        ->getPluginId();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getDatasourceId() {
    if (!isset($this->datasourceId)) {
      list($this->datasourceId) = Utility::splitCombinedId($this->itemId);
    }
    return $this->datasourceId;
  }

  /**
   * {@inheritdoc}
   */
  public function getDatasource() {
    if (!isset($this->datasource)) {
      $this->datasource = $this->index
        ->getDatasource($this
        ->getDatasourceId());
    }
    return $this->datasource;
  }

  /**
   * {@inheritdoc}
   */
  public function getIndex() {
    return $this->index;
  }

  /**
   * {@inheritdoc}
   */
  public function getLanguage() {
    if (!isset($this->language)) {
      $this->language = $this
        ->getDatasource()
        ->getItemLanguage($this
        ->getOriginalObject());
    }
    return $this->language;
  }

  /**
   * {@inheritdoc}
   */
  public function setLanguage($language) {
    $this->language = $language;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getId() {
    return $this->itemId;
  }

  /**
   * {@inheritdoc}
   */
  public function getOriginalObject($load = TRUE) {
    if (!isset($this->originalObject) && $load) {
      $this->originalObject = $this->index
        ->loadItem($this->itemId);
      if (!$this->originalObject) {
        throw new SearchApiException('Failed to load original object ' . $this->itemId);
      }
    }
    return $this->originalObject;
  }

  /**
   * {@inheritdoc}
   */
  public function setOriginalObject(ComplexDataInterface $original_object) {
    $this->originalObject = $original_object;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getField($field_id, $extract = TRUE) {
    if (isset($this->fields[$field_id])) {
      return $this->fields[$field_id];
    }
    $fields = $this
      ->getFields($extract);
    return isset($fields[$field_id]) ? $fields[$field_id] : NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getFields($extract = TRUE) {
    if ($extract && !$this->fieldsExtracted) {
      $data_type_fallback_mapping = \Drupal::getContainer()
        ->get('search_api.data_type_helper')
        ->getDataTypeFallbackMapping($this->index);
      foreach ([
        NULL,
        $this
          ->getDatasourceId(),
      ] as $datasource_id) {
        $fields_by_property_path = [];
        $processors_with_fields = [];
        $properties = $this->index
          ->getPropertyDefinitions($datasource_id);
        foreach ($this->index
          ->getFieldsByDatasource($datasource_id) as $field_id => $field) {

          // Don't overwrite fields that were previously set.
          if (empty($this->fields[$field_id])) {
            $this->fields[$field_id] = clone $field;
            $field_data_type = $this->fields[$field_id]
              ->getType();

            // If the field data type is in the fallback mapping list, then use
            // the fallback type as field type.
            if (isset($data_type_fallback_mapping[$field_data_type])) {
              $this->fields[$field_id]
                ->setType($data_type_fallback_mapping[$field_data_type]);
            }

            // For determining whether the field is provided via a processor, we
            // need to check using the first part of its property path (in other
            // words, the property that's directly on the result item, not
            // nested), since only direct properties of the item can be added by
            // the processor.
            $property = NULL;
            $property_name = Utility::splitPropertyPath($field
              ->getPropertyPath(), FALSE)[0];
            if (isset($properties[$property_name])) {
              $property = $properties[$property_name];
            }
            if ($property instanceof ProcessorPropertyInterface) {
              $processors_with_fields[$property
                ->getProcessorId()] = TRUE;
            }
            elseif ($datasource_id) {
              $fields_by_property_path[$field
                ->getPropertyPath()][] = $this->fields[$field_id];
            }
          }
        }
        try {
          if ($fields_by_property_path) {
            \Drupal::getContainer()
              ->get('search_api.fields_helper')
              ->extractFields($this
              ->getOriginalObject(), $fields_by_property_path, $this
              ->getLanguage());
          }
          if ($processors_with_fields) {
            $processors = $this->index
              ->getProcessorsByStage(ProcessorInterface::STAGE_ADD_PROPERTIES);
            foreach ($processors as $processor_id => $processor) {
              if (isset($processors_with_fields[$processor_id])) {
                $processor
                  ->addFieldValues($this);
              }
            }
          }
        } catch (SearchApiException $e) {

          // If we couldn't load the object, just log an error and fail
          // silently to set the values.
          $this
            ->logException($e);
        }
      }
      $this->fieldsExtracted = TRUE;
    }
    return $this->fields;
  }

  /**
   * {@inheritdoc}
   */
  public function setField($field_id, FieldInterface $field = NULL) {
    if ($field) {
      if ($field
        ->getFieldIdentifier() !== $field_id) {
        throw new \InvalidArgumentException('The field identifier passed must be consistent with the identifier set on the field object.');
      }

      // Make sure that the field has the same index object set as we. This
      // might otherwise cause impossibly hard-to-detect bugs.
      $field
        ->setIndex($this->index);
      $this->fields[$field_id] = $field;
    }
    else {
      unset($this->fields[$field_id]);
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function setFields(array $fields) {

    // Make sure that all fields have the same index object set as we. This
    // might otherwise cause impossibly hard-to-detect bugs.

    /** @var \Drupal\search_api\Item\FieldInterface $field */
    foreach ($fields as $field) {
      $field
        ->setIndex($this->index);
    }
    $this->fields = $fields;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isFieldsExtracted() {
    return $this->fieldsExtracted;
  }

  /**
   * {@inheritdoc}
   */
  public function setFieldsExtracted($fields_extracted) {
    $this->fieldsExtracted = $fields_extracted;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getScore() {
    return $this->score;
  }

  /**
   * {@inheritdoc}
   */
  public function setScore($score) {
    $this->score = $score;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getBoost() {
    return $this->boost;
  }

  /**
   * {@inheritdoc}
   */
  public function setBoost($boost) {
    $this->boost = $boost;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getExcerpt() {
    return $this->excerpt;
  }

  /**
   * {@inheritdoc}
   */
  public function setExcerpt($excerpt) {
    $this->excerpt = $excerpt;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function hasExtraData($key) {
    return array_key_exists($key, $this->extraData);
  }

  /**
   * {@inheritdoc}
   */
  public function getExtraData($key, $default = NULL) {
    return array_key_exists($key, $this->extraData) ? $this->extraData[$key] : $default;
  }

  /**
   * {@inheritdoc}
   */
  public function getAllExtraData() {
    return $this->extraData;
  }

  /**
   * {@inheritdoc}
   */
  public function setExtraData($key, $data = NULL) {
    if (isset($data)) {
      $this->extraData[$key] = $data;
    }
    else {
      unset($this->extraData[$key]);
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function checkAccess(AccountInterface $account = NULL) {
    @trigger_error('\\Drupal\\search_api\\Item\\ItemInterface::checkAccess() is deprecated in search_api:8.x-1.14 and is removed from search_api:2.0.0. Use getAccessResult() instead. See https://www.drupal.org/node/3051902', E_USER_DEPRECATED);
    return $this
      ->getAccessResult($account)
      ->isAllowed();
  }

  /**
   * {@inheritdoc}
   */
  public function getAccessResult(AccountInterface $account = NULL) {
    if (!$account) {
      $account = \Drupal::currentUser();
    }
    $uid = $account
      ->id();
    if (empty($this->accessResults[$uid])) {
      try {
        $this->accessResults[$uid] = $this
          ->getDatasource()
          ->getItemAccessResult($this
          ->getOriginalObject(), $account);
      } catch (SearchApiException $e) {
        $this->accessResults[$uid] = AccessResult::neutral('Item could not be loaded, so cannot check access');
      }
    }
    return $this->accessResults[$uid];
  }

  /**
   * {@inheritdoc}
   */
  public function getIterator() {
    return new \ArrayIterator($this
      ->getFields());
  }

  /**
   * Implements the magic __clone() method to implement a deep clone.
   */
  public function __clone() {

    // The fields definitely need to be cloned. For the extra data its hard (or,
    // rather, impossible) to tell, but we opt for cloning objects there, too,
    // to be on the (hopefully) safer side. (Ideas for later: introduce an
    // interface that tells us to not clone the data object; or check whether
    // its an entity; or introduce some other system to override this default.)
    foreach ($this->fields as $field_id => $field) {
      $this->fields[$field_id] = clone $field;
    }
    foreach ($this->extraData as $key => $data) {
      if (is_object($data)) {
        $this->extraData[$key] = clone $data;
      }
    }
  }

  /**
   * Implements the magic __sleep() method to avoid serializing the index.
   */
  public function __sleep() {
    $this->indexId = $this->index
      ->id();
    $properties = get_object_vars($this);

    // Don't serialize objects that can easily be loaded again. (We cannot be
    // sure about the "original object", so we do serialize that.
    unset($properties['index']);
    unset($properties['datasource']);
    unset($properties['accessResults']);
    return array_keys($properties);
  }

  /**
   * Implements the magic __wakeup() method to control object unserialization.
   */
  public function __wakeup() {

    // Make sure we have a container to do this. Otherwise, there could be
    // errors when displaying failed tests.
    if ($this->indexId && \Drupal::hasContainer()) {
      $this->index = \Drupal::entityTypeManager()
        ->getStorage('search_api_index')
        ->load($this->indexId);
      $this->indexId = NULL;
      if ($this->index && $this->fields) {
        foreach ($this->fields as $field) {
          $field
            ->setIndex($this->index);
        }
      }
    }
  }

  /**
   * Implements the magic __toString() method to simplify debugging.
   */
  public function __toString() {
    $out = 'Item ' . $this
      ->getId();
    if ($this
      ->getScore() != 1) {
      $out .= "\nScore: " . $this
        ->getScore();
    }
    if ($this
      ->getBoost() != 1) {
      $out .= "\nBoost: " . $this
        ->getBoost();
    }
    if ($this
      ->getExcerpt()) {
      $excerpt = str_replace("\n", "\n  ", $this
        ->getExcerpt());
      $out .= "\nExcerpt: {$excerpt}";
    }
    if ($this
      ->getFields(FALSE)) {
      $out .= "\nFields:";
      foreach ($this
        ->getFields(FALSE) as $field) {
        $field = str_replace("\n", "\n  ", "{$field}");
        $out .= "\n- " . $field;
      }
    }
    if ($this
      ->getAllExtraData()) {
      $data = str_replace("\n", "\n  ", print_r($this
        ->getAllExtraData(), TRUE));
      $out .= "\nExtra data: " . $data;
    }
    return $out;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
Item::$accessResults protected property Cached access results for the item, keyed by user ID.
Item::$boost protected property The boost of this item at indexing time.
Item::$datasource protected property The datasource of this item.
Item::$datasourceId protected property The ID of this item's datasource.
Item::$excerpt protected property The HTML text with highlighted text-parts that match the query.
Item::$extraData protected property Extra data set on this item.
Item::$fields protected property The extracted fields of this item.
Item::$fieldsExtracted protected property Whether the fields were already extracted for this item.
Item::$index protected property The search index with which this item is associated.
Item::$indexId protected property The ID of the index with which this item is associated.
Item::$itemId protected property The ID of this item.
Item::$language protected property The language code of this item.
Item::$originalObject protected property The complex data item this Search API item is based on.
Item::$score protected property The score this item had as a result in a corresponding search query.
Item::checkAccess public function Checks whether a user has permission to view this item. Overrides ItemInterface::checkAccess
Item::getAccessResult public function Checks whether a user has permission to view this item. Overrides ItemInterface::getAccessResult
Item::getAllExtraData public function Retrieves all extra data set for this item. Overrides ItemInterface::getAllExtraData
Item::getBoost public function Gets the boost value of this item. Overrides ItemInterface::getBoost
Item::getDatasource public function Returns the datasource of this item. Overrides ItemInterface::getDatasource
Item::getDatasourceId public function Returns the ID of this item's datasource. Overrides ItemInterface::getDatasourceId
Item::getExcerpt public function Returns an HTML text with highlighted text-parts that match the query. Overrides ItemInterface::getExcerpt
Item::getExtraData public function Retrieves extra data for this item. Overrides ItemInterface::getExtraData
Item::getField public function Retrieves a single field of this item. Overrides ItemInterface::getField
Item::getFields public function Returns the item's fields. Overrides ItemInterface::getFields
Item::getId public function Returns the item's ID. Overrides ItemInterface::getId
Item::getIndex public function Returns the index of this item. Overrides ItemInterface::getIndex
Item::getIterator public function
Item::getLanguage public function Retrieves the item language. Overrides ItemInterface::getLanguage
Item::getOriginalObject public function Returns the original complex data object this Search API item is based on. Overrides ItemInterface::getOriginalObject
Item::getScore public function Returns the score of the item. Overrides ItemInterface::getScore
Item::hasExtraData public function Determines whether extra data with a specific key is set on this item. Overrides ItemInterface::hasExtraData
Item::isFieldsExtracted public function Determines whether fields have been extracted already for this item. Overrides ItemInterface::isFieldsExtracted
Item::setBoost public function Sets the boost value of this item. Overrides ItemInterface::setBoost
Item::setExcerpt public function Sets an HTML text with highlighted text-parts that match the query. Overrides ItemInterface::setExcerpt
Item::setExtraData public function Sets some extra data for this item. Overrides ItemInterface::setExtraData
Item::setField public function Sets one of the item's fields. Overrides ItemInterface::setField
Item::setFields public function Sets the item's fields. Overrides ItemInterface::setFields
Item::setFieldsExtracted public function Sets the field extraction state of this item. Overrides ItemInterface::setFieldsExtracted
Item::setLanguage public function Sets the item language. Overrides ItemInterface::setLanguage
Item::setOriginalObject public function Sets the original complex data object this item should be based on. Overrides ItemInterface::setOriginalObject
Item::setScore public function Sets the score of the item. Overrides ItemInterface::setScore
Item::__clone public function Implements the magic __clone() method to implement a deep clone.
Item::__construct public function Constructs an Item object.
Item::__sleep public function Implements the magic __sleep() method to avoid serializing the index.
Item::__toString public function Implements the magic __toString() method to simplify debugging.
Item::__wakeup public function Implements the magic __wakeup() method to control object unserialization.
LoggerTrait::$logger protected property The logging channel to use.
LoggerTrait::getLogger public function Retrieves the logger.
LoggerTrait::logException protected function Logs an exception.
LoggerTrait::setLogger public function Sets the logger.