You are here

abstract class EntityStorageBase in Zircon Profile 8

Same name and namespace in other branches
  1. 8.0 core/lib/Drupal/Core/Entity/EntityStorageBase.php \Drupal\Core\Entity\EntityStorageBase

A base entity storage class.

Hierarchy

Expanded class hierarchy of EntityStorageBase

2 files declare their use of EntityStorageBase
ConfigEntityStorage.php in core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
Contains \Drupal\Core\Config\Entity\ConfigEntityStorage.
KeyValueEntityStorage.php in core/lib/Drupal/Core/Entity/KeyValueStore/KeyValueEntityStorage.php
Contains \Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage.

File

core/lib/Drupal/Core/Entity/EntityStorageBase.php, line 15
Contains \Drupal\Core\Entity\EntityStorageBase.

Namespace

Drupal\Core\Entity
View source
abstract class EntityStorageBase extends EntityHandlerBase implements EntityStorageInterface, EntityHandlerInterface {

  /**
   * Static cache of entities, keyed by entity ID.
   *
   * @var array
   */
  protected $entities = array();

  /**
   * Entity type ID for this storage.
   *
   * @var string
   */
  protected $entityTypeId;

  /**
   * Information about the entity type.
   *
   * The following code returns the same object:
   * @code
   * \Drupal::entityManager()->getDefinition($this->entityTypeId)
   * @endcode
   *
   * @var \Drupal\Core\Entity\EntityTypeInterface
   */
  protected $entityType;

  /**
   * Name of the entity's ID field in the entity database table.
   *
   * @var string
   */
  protected $idKey;

  /**
   * Name of entity's UUID database table field, if it supports UUIDs.
   *
   * Has the value FALSE if this entity does not use UUIDs.
   *
   * @var string
   */
  protected $uuidKey;

  /**
   * The name of the entity langcode property.
   *
   * @var string
   */
  protected $langcodeKey;

  /**
   * The UUID service.
   *
   * @var \Drupal\Component\Uuid\UuidInterface
   */
  protected $uuidService;

  /**
   * Name of the entity class.
   *
   * @var string
   */
  protected $entityClass;

  /**
   * Constructs an EntityStorageBase instance.
   *
   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
   *   The entity type definition.
   */
  public function __construct(EntityTypeInterface $entity_type) {
    $this->entityTypeId = $entity_type
      ->id();
    $this->entityType = $entity_type;
    $this->idKey = $this->entityType
      ->getKey('id');
    $this->uuidKey = $this->entityType
      ->getKey('uuid');
    $this->langcodeKey = $this->entityType
      ->getKey('langcode');
    $this->entityClass = $this->entityType
      ->getClass();
  }

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

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

  /**
   * {@inheritdoc}
   */
  public function loadUnchanged($id) {
    $this
      ->resetCache(array(
      $id,
    ));
    return $this
      ->load($id);
  }

  /**
   * {@inheritdoc}
   */
  public function resetCache(array $ids = NULL) {
    if ($this->entityType
      ->isStaticallyCacheable() && isset($ids)) {
      foreach ($ids as $id) {
        unset($this->entities[$id]);
      }
    }
    else {
      $this->entities = array();
    }
  }

  /**
   * Gets entities from the static cache.
   *
   * @param array $ids
   *   If not empty, return entities that match these IDs.
   *
   * @return \Drupal\Core\Entity\EntityInterface[]
   *   Array of entities from the entity cache.
   */
  protected function getFromStaticCache(array $ids) {
    $entities = array();

    // Load any available entities from the internal cache.
    if ($this->entityType
      ->isStaticallyCacheable() && !empty($this->entities)) {
      $entities += array_intersect_key($this->entities, array_flip($ids));
    }
    return $entities;
  }

  /**
   * Stores entities in the static entity cache.
   *
   * @param \Drupal\Core\Entity\EntityInterface[] $entities
   *   Entities to store in the cache.
   */
  protected function setStaticCache(array $entities) {
    if ($this->entityType
      ->isStaticallyCacheable()) {
      $this->entities += $entities;
    }
  }

  /**
   * Invokes a hook on behalf of the entity.
   *
   * @param string $hook
   *   One of 'presave', 'insert', 'update', 'predelete', 'delete', or
   *  'revision_delete'.
   * @param \Drupal\Core\Entity\EntityInterface  $entity
   *   The entity object.
   */
  protected function invokeHook($hook, EntityInterface $entity) {

    // Invoke the hook.
    $this
      ->moduleHandler()
      ->invokeAll($this->entityTypeId . '_' . $hook, array(
      $entity,
    ));

    // Invoke the respective entity-level hook.
    $this
      ->moduleHandler()
      ->invokeAll('entity_' . $hook, array(
      $entity,
    ));
  }

  /**
   * {@inheritdoc}
   */
  public function create(array $values = array()) {
    $entity_class = $this->entityClass;
    $entity_class::preCreate($this, $values);

    // Assign a new UUID if there is none yet.
    if ($this->uuidKey && $this->uuidService && !isset($values[$this->uuidKey])) {
      $values[$this->uuidKey] = $this->uuidService
        ->generate();
    }
    $entity = $this
      ->doCreate($values);
    $entity
      ->enforceIsNew();
    $entity
      ->postCreate($this);

    // Modules might need to add or change the data initially held by the new
    // entity object, for instance to fill-in default values.
    $this
      ->invokeHook('create', $entity);
    return $entity;
  }

  /**
   * Performs storage-specific creation of entities.
   *
   * @param array $values
   *   An array of values to set, keyed by property name.
   *
   * @return \Drupal\Core\Entity\EntityInterface
   */
  protected function doCreate(array $values) {
    return new $this->entityClass($values, $this->entityTypeId);
  }

  /**
   * {@inheritdoc}
   */
  public function load($id) {
    $entities = $this
      ->loadMultiple(array(
      $id,
    ));
    return isset($entities[$id]) ? $entities[$id] : NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function loadMultiple(array $ids = NULL) {
    $entities = array();

    // Create a new variable which is either a prepared version of the $ids
    // array for later comparison with the entity cache, or FALSE if no $ids
    // were passed. The $ids array is reduced as items are loaded from cache,
    // and we need to know if it's empty for this reason to avoid querying the
    // database when all requested entities are loaded from cache.
    $passed_ids = !empty($ids) ? array_flip($ids) : FALSE;

    // Try to load entities from the static cache, if the entity type supports
    // static caching.
    if ($this->entityType
      ->isStaticallyCacheable() && $ids) {
      $entities += $this
        ->getFromStaticCache($ids);

      // If any entities were loaded, remove them from the ids still to load.
      if ($passed_ids) {
        $ids = array_keys(array_diff_key($passed_ids, $entities));
      }
    }

    // Load any remaining entities from the database. This is the case if $ids
    // is set to NULL (so we load all entities) or if there are any ids left to
    // load.
    if ($ids === NULL || $ids) {
      $queried_entities = $this
        ->doLoadMultiple($ids);
    }

    // Pass all entities loaded from the database through $this->postLoad(),
    // which attaches fields (if supported by the entity type) and calls the
    // entity type specific load callback, for example hook_node_load().
    if (!empty($queried_entities)) {
      $this
        ->postLoad($queried_entities);
      $entities += $queried_entities;
    }
    if ($this->entityType
      ->isStaticallyCacheable()) {

      // Add entities to the cache.
      if (!empty($queried_entities)) {
        $this
          ->setStaticCache($queried_entities);
      }
    }

    // Ensure that the returned array is ordered the same as the original
    // $ids array if this was passed in and remove any invalid ids.
    if ($passed_ids) {

      // Remove any invalid ids from the array.
      $passed_ids = array_intersect_key($passed_ids, $entities);
      foreach ($entities as $entity) {
        $passed_ids[$entity
          ->id()] = $entity;
      }
      $entities = $passed_ids;
    }
    return $entities;
  }

  /**
   * Performs storage-specific loading of entities.
   *
   * Override this method to add custom functionality directly after loading.
   * This is always called, while self::postLoad() is only called when there are
   * actual results.
   *
   * @param array|null $ids
   *   (optional) An array of entity IDs, or NULL to load all entities.
   *
   * @return \Drupal\Core\Entity\EntityInterface[]
   *   Associative array of entities, keyed on the entity ID.
   */
  protected abstract function doLoadMultiple(array $ids = NULL);

  /**
   * Attaches data to entities upon loading.
   *
   * @param array $entities
   *   Associative array of query results, keyed on the entity ID.
   */
  protected function postLoad(array &$entities) {
    $entity_class = $this->entityClass;
    $entity_class::postLoad($this, $entities);

    // Call hook_entity_load().
    foreach ($this
      ->moduleHandler()
      ->getImplementations('entity_load') as $module) {
      $function = $module . '_entity_load';
      $function($entities, $this->entityTypeId);
    }

    // Call hook_TYPE_load().
    foreach ($this
      ->moduleHandler()
      ->getImplementations($this->entityTypeId . '_load') as $module) {
      $function = $module . '_' . $this->entityTypeId . '_load';
      $function($entities);
    }
  }

  /**
   * Maps from storage records to entity objects.
   *
   * @param array $records
   *   Associative array of query results, keyed on the entity ID.
   *
   * @return \Drupal\Core\Entity\EntityInterface[]
   *   An array of entity objects implementing the EntityInterface.
   */
  protected function mapFromStorageRecords(array $records) {
    $entities = array();
    foreach ($records as $record) {
      $entity = new $this->entityClass($record, $this->entityTypeId);
      $entities[$entity
        ->id()] = $entity;
    }
    return $entities;
  }

  /**
   * Determines if this entity already exists in storage.
   *
   * @param int|string $id
   *   The original entity ID.
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity being saved.
   *
   * @return bool
   */
  protected abstract function has($id, EntityInterface $entity);

  /**
   * {@inheritdoc}
   */
  public function delete(array $entities) {
    if (!$entities) {

      // If no entities were passed, do nothing.
      return;
    }

    // Ensure that the entities are keyed by ID.
    $keyed_entities = [];
    foreach ($entities as $entity) {
      $keyed_entities[$entity
        ->id()] = $entity;
    }

    // Allow code to run before deleting.
    $entity_class = $this->entityClass;
    $entity_class::preDelete($this, $keyed_entities);
    foreach ($keyed_entities as $entity) {
      $this
        ->invokeHook('predelete', $entity);
    }

    // Perform the delete and reset the static cache for the deleted entities.
    $this
      ->doDelete($keyed_entities);
    $this
      ->resetCache(array_keys($keyed_entities));

    // Allow code to run after deleting.
    $entity_class::postDelete($this, $keyed_entities);
    foreach ($keyed_entities as $entity) {
      $this
        ->invokeHook('delete', $entity);
    }
  }

  /**
   * Performs storage-specific entity deletion.
   *
   * @param \Drupal\Core\Entity\EntityInterface[] $entities
   *   An array of entity objects to delete.
   */
  protected abstract function doDelete($entities);

  /**
   * {@inheritdoc}
   */
  public function save(EntityInterface $entity) {

    // Track if this entity is new.
    $is_new = $entity
      ->isNew();

    // Execute presave logic and invoke the related hooks.
    $id = $this
      ->doPreSave($entity);

    // Perform the save and reset the static cache for the changed entity.
    $return = $this
      ->doSave($id, $entity);

    // Execute post save logic and invoke the related hooks.
    $this
      ->doPostSave($entity, !$is_new);
    return $return;
  }

  /**
   * Performs presave entity processing.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The saved entity.
   *
   * @return int|string
   *   The processed entity identifier.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   *   If the entity identifier is invalid.
   */
  protected function doPreSave(EntityInterface $entity) {
    $id = $entity
      ->id();

    // Track the original ID.
    if ($entity
      ->getOriginalId() !== NULL) {
      $id = $entity
        ->getOriginalId();
    }

    // Track if this entity exists already.
    $id_exists = $this
      ->has($id, $entity);

    // A new entity should not already exist.
    if ($id_exists && $entity
      ->isNew()) {
      throw new EntityStorageException("'{$this->entityTypeId}' entity with ID '{$id}' already exists.");
    }

    // Load the original entity, if any.
    if ($id_exists && !isset($entity->original)) {
      $entity->original = $this
        ->loadUnchanged($id);
    }

    // Allow code to run before saving.
    $entity
      ->preSave($this);
    $this
      ->invokeHook('presave', $entity);
    return $id;
  }

  /**
   * Performs storage-specific saving of the entity.
   *
   * @param int|string $id
   *   The original entity ID.
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity to save.
   *
   * @return bool|int
   *   If the record insert or update failed, returns FALSE. If it succeeded,
   *   returns SAVED_NEW or SAVED_UPDATED, depending on the operation performed.
   */
  protected abstract function doSave($id, EntityInterface $entity);

  /**
   * Performs post save entity processing.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The saved entity.
   * @param bool $update
   *   Specifies whether the entity is being updated or created.
   */
  protected function doPostSave(EntityInterface $entity, $update) {
    $this
      ->resetCache(array(
      $entity
        ->id(),
    ));

    // The entity is no longer new.
    $entity
      ->enforceIsNew(FALSE);

    // Allow code to run after saving.
    $entity
      ->postSave($this, $update);
    $this
      ->invokeHook($update ? 'update' : 'insert', $entity);

    // After saving, this is now the "original entity", and subsequent saves
    // will be updates instead of inserts, and updates must always be able to
    // correctly identify the original entity.
    $entity
      ->setOriginalId($entity
      ->id());
    unset($entity->original);
  }

  /**
   * Builds an entity query.
   *
   * @param \Drupal\Core\Entity\Query\QueryInterface $entity_query
   *   EntityQuery instance.
   * @param array $values
   *   An associative array of properties of the entity, where the keys are the
   *   property names and the values are the values those properties must have.
   */
  protected function buildPropertyQuery(QueryInterface $entity_query, array $values) {
    foreach ($values as $name => $value) {

      // Cast scalars to array so we can consistently use an IN condition.
      $entity_query
        ->condition($name, (array) $value, 'IN');
    }
  }

  /**
   * {@inheritdoc}
   */
  public function loadByProperties(array $values = array()) {

    // Build a query to fetch the entity IDs.
    $entity_query = $this
      ->getQuery();
    $this
      ->buildPropertyQuery($entity_query, $values);
    $result = $entity_query
      ->execute();
    return $result ? $this
      ->loadMultiple($result) : array();
  }

  /**
   * {@inheritdoc}
   */
  public function getQuery($conjunction = 'AND') {

    // Access the service directly rather than entity.query factory so the
    // storage's current entity type is used.
    return \Drupal::service($this
      ->getQueryServiceName())
      ->get($this->entityType, $conjunction);
  }

  /**
   * {@inheritdoc}
   */
  public function getAggregateQuery($conjunction = 'AND') {

    // Access the service directly rather than entity.query factory so the
    // storage's current entity type is used.
    return \Drupal::service($this
      ->getQueryServiceName())
      ->getAggregate($this->entityType, $conjunction);
  }

  /**
   * Gets the name of the service for the query for this entity storage.
   *
   * @return string
   *   The name of the service for the query for this entity storage.
   */
  protected abstract function getQueryServiceName();

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
EntityHandlerBase::$moduleHandler protected property The module handler to invoke hooks on. 3
EntityHandlerBase::moduleHandler protected function Gets the module handler. 3
EntityHandlerBase::setModuleHandler public function Sets the module handler for this handler.
EntityHandlerInterface::createInstance public static function Instantiates a new instance of this entity handler. 12
EntityStorageBase::$entities protected property Static cache of entities, keyed by entity ID. 1
EntityStorageBase::$entityClass protected property Name of the entity class.
EntityStorageBase::$entityType protected property Information about the entity type.
EntityStorageBase::$entityTypeId protected property Entity type ID for this storage.
EntityStorageBase::$idKey protected property Name of the entity's ID field in the entity database table.
EntityStorageBase::$langcodeKey protected property The name of the entity langcode property. 1
EntityStorageBase::$uuidKey protected property Name of entity's UUID database table field, if it supports UUIDs. 1
EntityStorageBase::$uuidService protected property The UUID service. 1
EntityStorageBase::buildPropertyQuery protected function Builds an entity query. 1
EntityStorageBase::create public function Constructs a new entity object, without permanently saving it. Overrides EntityStorageInterface::create 1
EntityStorageBase::delete public function Deletes permanently saved entities. Overrides EntityStorageInterface::delete 2
EntityStorageBase::doCreate protected function Performs storage-specific creation of entities. 3
EntityStorageBase::doDelete abstract protected function Performs storage-specific entity deletion. 3
EntityStorageBase::doLoadMultiple abstract protected function Performs storage-specific loading of entities. 4
EntityStorageBase::doPostSave protected function Performs post save entity processing. 1
EntityStorageBase::doPreSave protected function Performs presave entity processing. 1
EntityStorageBase::doSave abstract protected function Performs storage-specific saving of the entity. 3
EntityStorageBase::getAggregateQuery public function Gets an aggregated query instance. Overrides EntityStorageInterface::getAggregateQuery
EntityStorageBase::getEntityType public function Gets the entity type definition. Overrides EntityStorageInterface::getEntityType
EntityStorageBase::getEntityTypeId public function Gets the entity type ID. Overrides EntityStorageInterface::getEntityTypeId
EntityStorageBase::getFromStaticCache protected function Gets entities from the static cache. 1
EntityStorageBase::getQuery public function Gets an entity query instance. Overrides EntityStorageInterface::getQuery
EntityStorageBase::getQueryServiceName abstract protected function Gets the name of the service for the query for this entity storage. 4
EntityStorageBase::has abstract protected function Determines if this entity already exists in storage. 4
EntityStorageBase::invokeHook protected function Invokes a hook on behalf of the entity. 2
EntityStorageBase::load public function Loads one entity. Overrides EntityStorageInterface::load 2
EntityStorageBase::loadByProperties public function Load entities by their property values. Overrides EntityStorageInterface::loadByProperties 3
EntityStorageBase::loadMultiple public function Loads one or more entities. Overrides EntityStorageInterface::loadMultiple 2
EntityStorageBase::loadUnchanged public function Loads an unchanged entity from the database. Overrides EntityStorageInterface::loadUnchanged
EntityStorageBase::mapFromStorageRecords protected function Maps from storage records to entity objects. 3
EntityStorageBase::postLoad protected function Attaches data to entities upon loading.
EntityStorageBase::resetCache public function Resets the internal, static entity cache. Overrides EntityStorageInterface::resetCache 2
EntityStorageBase::save public function Saves the entity permanently. Overrides EntityStorageInterface::save 4
EntityStorageBase::setStaticCache protected function Stores entities in the static entity cache. 1
EntityStorageBase::__construct public function Constructs an EntityStorageBase instance. 3
EntityStorageInterface::deleteRevision public function Delete a specific entity revision. 3
EntityStorageInterface::FIELD_LOAD_CURRENT constant Load the most recent version of an entity's field data.
EntityStorageInterface::FIELD_LOAD_REVISION constant Load the version of an entity's field data specified in the entity.
EntityStorageInterface::loadRevision public function Load a specific entity revision. 3
StringTranslationTrait::$stringTranslation protected property The string translation service.
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.