You are here

abstract class EdgeEntityStorageBase in Apigee Edge 8

Base entity storage class for Apigee Edge entities.

Hierarchy

Expanded class hierarchy of EdgeEntityStorageBase

1 file declares its use of EdgeEntityStorageBase
AppForm.php in src/Entity/Form/AppForm.php

File

src/Entity/Storage/EdgeEntityStorageBase.php, line 41

Namespace

Drupal\apigee_edge\Entity\Storage
View source
abstract class EdgeEntityStorageBase extends DrupalEntityStorageBase implements EdgeEntityStorageInterface {

  /**
   * Initial status for saving a item to Apigee Edge.
   *
   * Similar to SAVED_NEW and SAVED_UPDATED. If this is returned then
   * something probably went wrong.
   *
   * @var int
   */
  public const SAVED_UNKNOWN = 0;

  /**
   * Cache backend.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $cacheBackend;

  /**
   * Number of seconds until an entity can be served from cache.
   *
   * -1 is also an allowed, which means the item should never be removed unless
   * explicitly deleted.
   *
   * @var int
   */
  protected $cacheExpiration = CacheBackendInterface::CACHE_PERMANENT;

  /**
   * The system time.
   *
   * @var \Drupal\Component\Datetime\TimeInterface
   */
  protected $systemTime;

  /**
   * Constructs an EdgeEntityStorageBase instance.
   *
   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
   *   The entity type definition.
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
   *   The cache backend to be used.
   * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface $memory_cache
   *   The memory cache.
   * @param \Drupal\Component\Datetime\TimeInterface $system_time
   *   The system time.
   */
  public function __construct(EntityTypeInterface $entity_type, CacheBackendInterface $cache_backend, MemoryCacheInterface $memory_cache, TimeInterface $system_time) {
    parent::__construct($entity_type, $memory_cache);
    $this->cacheBackend = $cache_backend;
    $this->systemTime = $system_time;
  }

  /**
   * {@inheritdoc}
   */
  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
    return new static($entity_type, $container
      ->get('cache.apigee_edge_entity'), $container
      ->get('entity.memory_cache'), $container
      ->get('datetime.time'));
  }

  /**
   * {@inheritdoc}
   */
  protected function doLoadMultiple(array $ids = NULL) {

    // Attempt to load entities from the persistent cache. This will remove IDs
    // that were loaded from $ids.
    $entities_from_cache = $this
      ->getFromPersistentCache($ids);
    return $entities_from_cache + $this
      ->getFromStorage($ids);
  }

  /**
   * {@inheritdoc}
   */
  public function loadUnchanged($id) {
    $this
      ->resetControllerCache([
      $id,
    ]);
    return parent::loadUnchanged($id);
  }

  /**
   * Resets entity controller's cache if it is a cached entity controller.
   *
   * @param string[] $ids
   *   Array of entity ids.
   */
  protected function resetControllerCache(array $ids) {
    $controller = $this
      ->entityController();
    if ($controller instanceof EntityCacheAwareControllerInterface) {
      $controller
        ->entityCache()
        ->removeEntities($ids);
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function has($id, EntityInterface $entity) {
    return !$entity
      ->isNew();
  }

  /**
   * {@inheritdoc}
   */
  protected function doDelete($entities) {
    $this
      ->withController(function (EdgeEntityControllerInterface $controller) use ($entities) {
      foreach ($entities as $entity) {

        /** @var \Drupal\Core\Entity\EntityInterface $entity */
        $controller
          ->delete($entity
          ->id());
      }
    });
  }

  /**
   * {@inheritdoc}
   */
  protected function doSave($id, EntityInterface $entity) {
    $result = static::SAVED_UNKNOWN;
    $this
      ->withController(function (EdgeEntityControllerInterface $controller) use ($id, $entity, &$result) {

      /** @var \Drupal\apigee_edge\Entity\EdgeEntityInterface $entity */
      if ($entity
        ->isNew()) {
        $controller
          ->create($entity
          ->decorated());
        $result = SAVED_NEW;
      }
      else {
        $controller
          ->update($entity
          ->decorated());
        $result = SAVED_UPDATED;
      }
    });
    return $result;
  }

  /**
   * {@inheritdoc}
   */
  protected function getQueryServiceName() {
    return 'entity.query.edge';
  }

  /**
   * {@inheritdoc}
   */
  public function loadRevision($revision_id) {
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function deleteRevision($revision_id) {
  }

  /**
   * Returns the wrapped controller instance used by this storage.
   *
   * @return \Drupal\apigee_edge\Entity\Controller\EdgeEntityControllerInterface
   *   The entity controller interface with CRUDL capabilities.
   */
  protected abstract function entityController() : EdgeEntityControllerInterface;

  /**
   * Wraps communication with Apigee Edge.
   *
   * This function converts exceptions from Apigee Edge into
   * EntityStorageException and logs the original exceptions.
   *
   * @param callable $action
   *   Communication to perform.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   *   The converted exception.
   */
  protected function withController(callable $action) {
    try {
      $action($this
        ->entityController());
    } catch (\Exception $ex) {
      throw new EntityStorageException($ex
        ->getMessage(), $ex
        ->getCode(), $ex);
    }
  }

  /**
   * Creates a new Drupal entity from an SDK entity.
   *
   * @param \Apigee\Edge\Entity\EntityInterface $sdk_entity
   *   An SDK entity.
   *
   * @return \Drupal\apigee_edge\Entity\EdgeEntityInterface
   *   The Drupal entity that decorates the SDK entity.
   */
  protected function createNewInstance(SdkEntityInterface $sdk_entity) : DrupalEdgeEntityInterface {
    $rc = new \ReflectionClass($this->entityClass);
    $rm = $rc
      ->getMethod('createFrom');
    return $rm
      ->invoke(NULL, $sdk_entity);
  }

  /**
   * Gets entities from the storage.
   *
   * @param array|null $ids
   *   If not empty, return entities that match these IDs. Return all entities
   *   when NULL.
   *
   * @return \Drupal\Core\Entity\EntityInterface[]
   *   Array of entities from the storage.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function getFromStorage(array $ids = NULL) {
    $entities = [];

    // If ids is an empty array there is nothing to do.
    // Probably every entities could have been found in the persistent cache.
    // Node::loadMultiple() works the same.
    if ($ids === []) {
      return $entities;
    }
    $this
      ->withController(function (EdgeEntityControllerInterface $controller) use ($ids, &$entities) {
      $tmp = [];

      // Speed up things by loading only one entity.
      if ($ids !== NULL && count($ids) === 1) {

        // TODO When user's email changes do not ask Apigee Edge 3 times
        // whether a developer exists with the new email address or not.
        try {
          $entity = $controller
            ->load(reset($ids));
          $tmp[$entity
            ->id()] = $entity;
        } catch (ApiException $e) {

          // Entity with id may not exists.
        }
      }
      else {

        // There is nothing else we could do we have to load all entities
        // from Apigee Edge.
        $tmp = $controller
          ->loadAll();
      }
      $entities = $this
        ->processLoadedEntities($ids, $tmp);
    });
    return $entities;
  }

  /**
   * Processes loaded (SDK) entities to Drupal entities.
   *
   * This method also ensured that storage hooks gets called and entities
   * gets saved to the persistent cache before they gets returned.
   *
   * @param array|null $ids
   *   Originally request entity ids.
   * @param array $sdk_entities
   *   The loaded SDK entities by the entity controller for the requested ids.
   *
   * @return array
   *   Array of Drupal entities.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   *   If Drupal entity ids could not be resolved.
   */
  protected final function processLoadedEntities(?array $ids, array $sdk_entities) : array {
    $entities = [];

    // Returned entities are SDK entities and not Drupal entities,
    // what if the id is used in Drupal is different than what
    // SDK uses? (ex.: developer)
    foreach ($sdk_entities as $entity) {
      $drupal_entity = $this
        ->createNewInstance($entity);
      if ($ids === NULL) {
        $entities[$drupal_entity
          ->id()] = $drupal_entity;
      }
      elseif ($referenced_ids = array_intersect($drupal_entity
        ->uniqueIds(), $ids)) {
        if (count($referenced_ids) > 1) {

          // Sanity check, why would someone try to load the same entity
          // by using more than one of its unique id.
          throw new EntityStorageException(sprintf('The same entity should be referenced only with one id, got %s.', implode('', $referenced_ids)));
        }
        $entities[reset($referenced_ids)] = $drupal_entity;
      }
    }
    $this
      ->invokeStorageLoadHook($entities);
    $this
      ->setPersistentCache($entities);
    return $entities;
  }

  /**
   * Invokes hook_entity_storage_load().
   *
   * @param \Drupal\Core\Entity\EntityInterface[] $entities
   *   List of entities, keyed on the entity ID.
   */
  protected function invokeStorageLoadHook(array &$entities) {
    if (!empty($entities)) {

      // Call hook_entity_storage_load().
      foreach ($this
        ->moduleHandler()
        ->getImplementations('entity_storage_load') as $module) {
        $function = $module . '_entity_storage_load';
        $function($entities, $this->entityTypeId);
      }

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

  /**
   * Gets entities from the persistent cache backend.
   *
   * @param array|null &$ids
   *   If not empty, return entities that match these IDs. IDs that were found
   *   will be removed from the list.
   *
   * @return \Drupal\Core\Entity\EntityInterface[]
   *   Array of entities from the persistent cache.
   */
  protected function getFromPersistentCache(array &$ids = NULL) {
    if (!$this->entityType
      ->isPersistentlyCacheable() || empty($ids)) {
      return [];
    }
    $entities = [];

    // Build the list of cache entries to retrieve.
    $cid_map = [];
    foreach ($ids as $id) {
      $cid_map[$id] = $this
        ->buildCacheId($id);
    }
    $cids = array_values($cid_map);
    if ($cache = $this->cacheBackend
      ->getMultiple($cids)) {

      // Get the entities that were found in the cache.
      foreach ($ids as $index => $id) {
        $cid = $cid_map[$id];
        if (isset($cache[$cid])) {
          $entities[$id] = $cache[$cid]->data;
          unset($ids[$index]);
        }
      }
    }
    return $entities;
  }

  /**
   * Stores entities in the persistent cache backend.
   *
   * @param \Drupal\Core\Entity\EntityInterface[] $entities
   *   Entities to store in the cache.
   */
  protected function setPersistentCache(array $entities) {
    if (!$this->entityType
      ->isPersistentlyCacheable()) {
      return;
    }
    foreach ($entities as $id => $entity) {
      $this->cacheBackend
        ->set($this
        ->buildCacheId($id), $entity, $this
        ->getPersistentCacheExpiration(), $this
        ->getPersistentCacheTags($entity));
    }
  }

  /**
   * Number of seconds after a cache item expires.
   *
   * So our "persistent cache" implementation could be actually not a persistent
   * one but we kept using this naming convention by hoping that the persistent
   * caching features becomes decoupled from ContentEntityStorageBase and we
   * could build on the top of that solution with as less pain as possible.
   *
   * @return int
   *   Number of seconds after a cache item expires.
   */
  protected function getPersistentCacheExpiration() {
    if ($this->cacheExpiration !== CacheBackendInterface::CACHE_PERMANENT) {
      return $this->systemTime
        ->getCurrentTime() + $this->cacheExpiration;
    }
    return $this->cacheExpiration;
  }

  /**
   * Generates cache tags for entities.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   An entity object.
   *
   * @return array
   *   Array of cache tags.
   */
  protected function getPersistentCacheTags(EntityInterface $entity) {
    return [
      "{$this->entityTypeId}",
      "{$this->entityTypeId}:values",
      "{$this->entityTypeId}:{$entity->id()}",
      "{$this->entityTypeId}:{$entity->id()}:values",
    ];
  }

  /**
   * Builds the cache ID for the passed in entity ID.
   *
   * @param int $id
   *   Entity ID for which the cache ID should be built.
   *
   * @return string
   *   Cache ID that can be passed to the cache backend.
   */
  protected function buildCacheId($id) {
    return "values:{$this->entityTypeId}:{$id}";
  }

  /**
   * {@inheritdoc}
   */
  public function resetCache(array $ids = NULL) {
    if ($this->entityType
      ->isStaticallyCacheable() && $ids) {
      $cids = [];
      foreach ($ids as $id) {
        $cid = $this
          ->buildCacheId($id);
        $cids[] = $cid;
        $this->memoryCache
          ->delete($cid);
      }
      if ($this->entityType
        ->isPersistentlyCacheable()) {
        $this->cacheBackend
          ->deleteMultiple($cids);
      }
    }
    else {
      $this->memoryCache
        ->invalidateTags([
        $this->memoryCacheTag,
      ]);
      if ($this->entityType
        ->isPersistentlyCacheable()) {
        Cache::invalidateTags([
          $this->entityTypeId . ':values',
        ]);
      }
    }

    // We do not clear the entity controller's cache here because our main goal
    // with the entity controller cache to reduce the API calls that we
    // send to Apigee Edge. Although we do delete the entity controller's cache
    // when it is necessary, like in loadUnchanged().
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
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
EdgeEntityStorageBase::$cacheBackend protected property Cache backend.
EdgeEntityStorageBase::$cacheExpiration protected property Number of seconds until an entity can be served from cache.
EdgeEntityStorageBase::$systemTime protected property The system time.
EdgeEntityStorageBase::buildCacheId protected function Builds the cache ID for the passed in entity ID. Overrides EntityStorageBase::buildCacheId
EdgeEntityStorageBase::createInstance public static function Instantiates a new instance of this entity handler. Overrides EntityHandlerInterface::createInstance 4
EdgeEntityStorageBase::createNewInstance protected function Creates a new Drupal entity from an SDK entity.
EdgeEntityStorageBase::deleteRevision public function Delete a specific entity revision. Overrides EntityStorageInterface::deleteRevision
EdgeEntityStorageBase::doDelete protected function Performs storage-specific entity deletion. Overrides EntityStorageBase::doDelete 1
EdgeEntityStorageBase::doLoadMultiple protected function Performs storage-specific loading of entities. Overrides EntityStorageBase::doLoadMultiple
EdgeEntityStorageBase::doSave protected function Performs storage-specific saving of the entity. Overrides EntityStorageBase::doSave 2
EdgeEntityStorageBase::entityController abstract protected function Returns the wrapped controller instance used by this storage. 5
EdgeEntityStorageBase::getFromPersistentCache protected function Gets entities from the persistent cache backend.
EdgeEntityStorageBase::getFromStorage protected function Gets entities from the storage. 1
EdgeEntityStorageBase::getPersistentCacheExpiration protected function Number of seconds after a cache item expires.
EdgeEntityStorageBase::getPersistentCacheTags protected function Generates cache tags for entities. 2
EdgeEntityStorageBase::getQueryServiceName protected function Gets the name of the service for the query for this entity storage. Overrides EntityStorageBase::getQueryServiceName
EdgeEntityStorageBase::has protected function Determines if this entity already exists in storage. Overrides EntityStorageBase::has
EdgeEntityStorageBase::invokeStorageLoadHook protected function Invokes hook_entity_storage_load().
EdgeEntityStorageBase::loadRevision public function Load a specific entity revision. Overrides EntityStorageInterface::loadRevision
EdgeEntityStorageBase::loadUnchanged public function Loads an unchanged entity from the database. Overrides EntityStorageBase::loadUnchanged 1
EdgeEntityStorageBase::processLoadedEntities final protected function Processes loaded (SDK) entities to Drupal entities.
EdgeEntityStorageBase::resetCache public function Resets the internal, static entity cache. Overrides EntityStorageBase::resetCache 2
EdgeEntityStorageBase::resetControllerCache protected function Resets entity controller's cache if it is a cached entity controller.
EdgeEntityStorageBase::SAVED_UNKNOWN public constant Initial status for saving a item to Apigee Edge.
EdgeEntityStorageBase::setPersistentCache protected function Stores entities in the persistent cache backend. 2
EdgeEntityStorageBase::withController protected function Wraps communication with Apigee Edge.
EdgeEntityStorageBase::__construct public function Constructs an EdgeEntityStorageBase instance. Overrides EntityStorageBase::__construct 4
EntityHandlerBase::$moduleHandler protected property The module handler to invoke hooks on. 2
EntityHandlerBase::moduleHandler protected function Gets the module handler. 2
EntityHandlerBase::setModuleHandler public function Sets the module handler for this handler.
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::$memoryCache protected property The memory cache.
EntityStorageBase::$memoryCacheTag protected property The memory cache cache tag.
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::doPostSave protected function Performs post save entity processing. 1
EntityStorageBase::doPreSave protected function Performs presave entity processing. 1
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.
EntityStorageBase::getQuery public function Gets an entity query instance. Overrides EntityStorageInterface::getQuery
EntityStorageBase::hasData public function Determines if the storage contains any data. Overrides EntityStorageInterface::hasData 3
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 1
EntityStorageBase::mapFromStorageRecords protected function Maps from storage records to entity objects. 4
EntityStorageBase::postLoad protected function Attaches data to entities upon loading.
EntityStorageBase::preLoad protected function Gathers entities from a 'preload' step. 1
EntityStorageBase::restore public function Restores a previously saved entity. Overrides EntityStorageInterface::restore 1
EntityStorageBase::save public function Saves the entity permanently. Overrides EntityStorageInterface::save 4
EntityStorageBase::setStaticCache protected function Stores entities in the static entity cache.
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.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
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.