You are here

class EntityCacheControllerHelper in Entity cache 7

Entity cache helper.

Note: while this class is not a real entity controller it needs to extend DrupalDefaultEntityController to get access to protected properties.

Hierarchy

Expanded class hierarchy of EntityCacheControllerHelper

1 string reference to 'EntityCacheControllerHelper'
entitycache.module in ./entitycache.module
Allows for caching of core entities.

File

includes/entitycache.entitycachecontrollerhelper.inc, line 14
EntityCacheControllerHelper cache helper.

View source
class EntityCacheControllerHelper extends DrupalDefaultEntityController {
  public static function resetEntityCache($controller, array $ids = NULL) {
    drupal_alter('entitycache_pre_reset_cache', $ids, $controller->entityType);

    // Allow other modules to disable this cache clear, which is useful if
    // hook_entitycache_pre_cache_get() and hook_entitycache_pre_cache_set()
    // are used to prevent items to enter the cache.
    if ($ids === FALSE) {
      return;
    }

    // Reset the persistent cache.
    if (!empty($ids)) {
      cache_clear_all($ids, 'cache_entity_' . $controller->entityType);
    }
    else {

      // Force all cached entries to be deleted.
      cache_clear_all('*', 'cache_entity_' . $controller->entityType, TRUE);
    }

    // Give modules the chance to act on any entity.
    foreach (module_implements('entitycache_reset') as $module) {
      $function = $module . '_entitycache_reset';
      $function($ids, $controller->entityType);
    }

    // Give modules the chance to act on a specific entity type.
    foreach (module_implements('entitycache_' . $controller->entityType . '_reset') as $module) {
      $function = $module . '_entitycache_' . $controller->entityType . '_reset';
      $function($ids);
    }
  }

  /**
   * Loads entities by IDs/conditions, which are potentially cached.
   *
   * @param \DrupalDefaultEntityController $controller
   *   The entity (storage) controller.
   *
   * @param array $ids
   *   (optional) entity IDs to load.
   * @param array $conditions
   *   (options) An array of conditions to be used when loading. Note: It is
   *   possible to pass conditions with and without IDs.
   *
   * @return array
   *   An array of loaded entities keyed by ID.
   */
  public static function entityCacheLoad($controller, $ids = array(), $conditions = array()) {

    // @todo Convert this to static::method() for Drupal 8.
    $class = get_called_class();
    $entities = array();
    $cached_entities = array();
    $queried_entities = array();

    // Revisions are not statically cached, and require a different query to
    // other conditions, so separate the revision id into its own variable.
    if ($controller->revisionKey && isset($conditions[$controller->revisionKey])) {
      $revision_id = $conditions[$controller->revisionKey];
      unset($conditions[$controller->revisionKey]);
    }
    else {
      $revision_id = FALSE;
    }

    // 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;

    // Use an entity field query to transform the list of conditions into
    // the set of entity IDs which the conditions admit, then re-enter this
    // method with that set as the $ids constraint.
    if ($conditions) {
      $query = new EntityFieldQuery();
      $query
        ->entityCondition('entity_type', $controller->entityType);
      foreach ($conditions as $property_name => $condition) {

        // Note $condition might be multiple values, which are treated as OR
        // by default.
        $query
          ->propertyCondition($property_name, $condition);
      }

      // Limit the result set also by the passed in IDs.
      if ($passed_ids) {
        $query
          ->propertyCondition($controller->idKey, array_keys($passed_ids));
      }
      $result = $query
        ->execute();
      if (isset($result[$controller->entityType])) {
        $entity_ids = array_keys($result[$controller->entityType]);
        if ($revision_id) {
          return $class::entityCacheLoad($controller, $entity_ids, array(
            $controller->revisionKey => $revision_id,
          ));
        }
        else {
          return $class::entityCacheLoad($controller, $entity_ids);
        }
      }
      else {

        // No results found.
        return array();
      }
    }

    // Try to load entities from the static cache, if the entity type supports
    // static caching.
    if ($controller->cache && !$revision_id) {
      $entities += $controller
        ->cacheGet($ids, $conditions);

      // 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));
      }
    }
    if (!empty($controller->entityInfo['entity cache']) && !$revision_id && $ids && !$conditions) {
      $entities += $cached_entities = self::entityCacheGet($controller, $ids, $conditions);

      // If any entities were loaded, remove them from the ids still to load.
      $ids = array_diff($ids, array_keys($cached_entities));
      if ($controller->cache) {

        // Add entities to the cache if we are not loading a revision.
        if (!empty($cached_entities) && !$revision_id) {
          $controller
            ->cacheSet($cached_entities);
        }
      }
    }

    // Ensure integer entity IDs are valid.
    if (!empty($ids)) {
      $class::entityCacheCleanIds($controller, $ids);
    }

    // Load any remaining entities from the database. This is the case if $ids
    // is set to FALSE (so we load all entities), if there are any ids left to
    // load, if loading a revision, or if $conditions was passed without $ids.
    if ($ids === FALSE || $ids || $revision_id || $conditions && !$passed_ids) {

      // Build the query.
      $query = $controller
        ->buildQuery($ids, $conditions, $revision_id);
      $queried_entities = $query
        ->execute()
        ->fetchAllAssoc($controller->idKey);
    }

    // Pass all entities loaded from the database through $controller->attachLoad(),
    // 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)) {
      $controller
        ->attachLoad($queried_entities, $revision_id);
      $entities += $queried_entities;
    }
    if (!empty($controller->entityInfo['entity cache'])) {

      // Add entities to the entity cache if we are not loading a revision.
      if (!empty($queried_entities) && !$revision_id) {

        // Only cache the entities which were loaded by ID. Entities that were
        // loaded based on conditions will never be found via cacheGet() and we
        // would keep on caching them.
        if ($passed_ids) {
          $queried_entities_by_id = array_intersect_key($queried_entities, $passed_ids);
          if (!empty($queried_entities_by_id)) {
            self::entityCacheSet($controller, $queried_entities_by_id);
          }
        }
      }
    }
    if ($controller->cache) {

      // Add entities to the cache if we are not loading a revision.
      if (!empty($queried_entities) && !$revision_id) {
        $controller
          ->cacheSet($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->{$controller->idKey}] = $entity;
      }
      $entities = $passed_ids;
    }
    return $entities;
  }

  /**
   * Ensures integer entity IDs are valid.
   *
   * The identifier sanitization provided by this method has been introduced
   * as Drupal used to rely on the database to facilitate this, which worked
   * correctly with MySQL but led to errors with other DBMS such as PostgreSQL.
   *
   * @param array $ids
   *   The entity IDs to verify.
   *
   * @return array
   *   The sanitized list of entity IDs.
   */
  protected static function entityCacheCleanIds($controller, &$ids) {
    $entity_info = entity_get_info($controller->entityType);
    if (isset($entity_info['base table field types'])) {
      $id_type = $entity_info['base table field types'][$controller->idKey];
      if ($id_type == 'serial' || $id_type == 'int') {
        $ids = array_filter($ids, array(
          __CLASS__,
          'entityCacheFilterId',
        ));
        $ids = array_map('intval', $ids);
      }
    }
  }

  /**
   * Callback for array_filter that removes non-integer IDs.
   */
  public static function entityCacheFilterId($id) {
    return is_numeric($id) && $id == (int) $id;
  }
  public static function entityCacheGet($controller, $ids, $conditions = array()) {
    drupal_alter('entitycache_pre_cache_get', $ids, $conditions, $controller->entityType);
    $cached_entities = array();
    if ($ids && !$conditions) {
      $cached = cache_get_multiple($ids, 'cache_entity_' . $controller->entityType);
      if ($cached) {
        foreach ($cached as $item) {
          $cached_entities[$item->cid] = $item->data;
        }
        self::entityCacheAttachLoad($controller, $cached_entities);
      }
    }
    return $cached_entities;
  }
  public static function entityCacheSet($controller, $entities) {
    drupal_alter('entitycache_pre_cache_set', $entities, $controller->entityType);
    foreach ($entities as $id => $item) {
      cache_set($id, $item, 'cache_entity_' . $controller->entityType);
    }
    drupal_alter('entitycache_post_cache_set', $entities, $controller->entityType);
  }

  /**
   * Allow modules to implement uncached entity hooks.
   *
   * Perform two additional hook invocations for modules needing to add
   * uncacheable data to objects while serving the request.
   *
   * @see entitycache_entitycache_node_load()
   */
  public static function entityCacheAttachLoad($controller, $entities) {

    // Give modules the chance to act on any entity.
    foreach (module_implements('entitycache_load') as $module) {
      $function = $module . '_entitycache_load';
      $function($entities, $controller->entityType);
    }

    // Give modules the chance to act on a specific entity type.
    foreach (module_implements('entitycache_' . $controller->entityType . '_load') as $module) {
      $function = $module . '_entitycache_' . $controller->entityType . '_load';
      $function($entities);
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DrupalDefaultEntityController::$cache protected property Whether this entity type should use the static cache.
DrupalDefaultEntityController::$entityCache protected property Static cache of entities, keyed by entity ID.
DrupalDefaultEntityController::$entityInfo protected property Array of information about the entity.
DrupalDefaultEntityController::$entityType protected property Entity type for this controller instance.
DrupalDefaultEntityController::$hookLoadArguments protected property Additional arguments to pass to hook_TYPE_load().
DrupalDefaultEntityController::$idKey protected property Name of the entity's ID field in the entity database table.
DrupalDefaultEntityController::$revisionKey protected property Name of entity's revision database table field, if it supports revisions.
DrupalDefaultEntityController::$revisionTable protected property The table that stores revisions, if the entity supports revisions.
DrupalDefaultEntityController::attachLoad protected function Attaches data to entities upon loading. 4
DrupalDefaultEntityController::buildQuery protected function Builds the query to load the entity. 4
DrupalDefaultEntityController::cacheGet protected function Gets entities from the static cache. 1
DrupalDefaultEntityController::cacheSet protected function Stores entities in the static entity cache.
DrupalDefaultEntityController::cleanIds protected function Ensures integer entity IDs are valid.
DrupalDefaultEntityController::filterId protected function Callback for array_filter that removes non-integer IDs.
DrupalDefaultEntityController::load public function Implements DrupalEntityControllerInterface::load(). Overrides DrupalEntityControllerInterface::load
DrupalDefaultEntityController::resetCache public function Implements DrupalEntityControllerInterface::resetCache(). Overrides DrupalEntityControllerInterface::resetCache
DrupalDefaultEntityController::__construct public function Constructor: sets basic variables.
EntityCacheControllerHelper::entityCacheAttachLoad public static function Allow modules to implement uncached entity hooks.
EntityCacheControllerHelper::entityCacheCleanIds protected static function Ensures integer entity IDs are valid.
EntityCacheControllerHelper::entityCacheFilterId public static function Callback for array_filter that removes non-integer IDs.
EntityCacheControllerHelper::entityCacheGet public static function
EntityCacheControllerHelper::entityCacheLoad public static function Loads entities by IDs/conditions, which are potentially cached.
EntityCacheControllerHelper::entityCacheSet public static function
EntityCacheControllerHelper::resetEntityCache public static function