diff --git a/entitycache.defaultentitycontroller.inc b/entitycache.defaultentitycontroller.inc new file mode 100644 index 0000000..296d66d --- /dev/null +++ b/entitycache.defaultentitycontroller.inc @@ -0,0 +1,19 @@ + $controller) { - $entity_info[$type]['field cache'] = FALSE; - $entity_info[$type]['entity cache'] = TRUE; - $entity_info[$type]['controller class'] = $controller; - } -} - -/** * Entity cache helper. * * Note: while this class is not a real entity controller it needs to extend @@ -235,31 +224,6 @@ class EntityCacheControllerHelper extends DrupalDefaultEntityController { } } -/** - * Default entity controller with persistent cache. - */ -class EntityCacheDefaultEntityController extends DrupalDefaultEntityController { - public function resetCache(array $ids = NULL) { - EntityCacheControllerHelper::resetEntityCache($this, $ids); - parent::resetCache($ids); - } - public function load($ids = array(), $conditions = array()) { - return EntityCacheControllerHelper::entityCacheLoad($this, $ids, $conditions); - } -} - -/** - * Node entity controller with persistent cache. - */ -class EntityCacheNodeController extends NodeController { - public function resetCache(array $ids = NULL) { - EntityCacheControllerHelper::resetEntityCache($this, $ids); - parent::resetCache($ids); - } - public function load($ids = array(), $conditions = array()) { - return EntityCacheControllerHelper::entityCacheLoad($this, $ids, $conditions); - } -} /** * Implements hook_flush_caches(). @@ -272,201 +236,3 @@ function entitycache_flush_caches() { } return $bins; } - -/** - * User entity controller with persistent cache. - */ -class EntityCacheUserController extends UserController { - public function resetCache(array $ids = NULL) { - EntityCacheControllerHelper::resetEntityCache($this, $ids); - parent::resetCache($ids); - } - public function load($ids = array(), $conditions = array()) { - return EntityCacheControllerHelper::entityCacheLoad($this, $ids, $conditions); - } -} - -/** - * Helper function to list all supported core entities. - * - * @param $enabled - * If set, only return enabled modules. - * - * @return - * An array of core entities. - */ -function entitycache_supported_core_entities($enabled = FALSE) { - $return = array( - 'comment' => 'EntityCacheCommentController', - 'file' => 'EntityCacheDefaultEntityController', - 'node' => 'EntityCacheNodeController', - 'taxonomy_term' => 'EntityCacheTaxonomyTermController', - 'taxonomy_vocabulary' => 'EntityCacheTaxonomyVocabularyController', - 'user' => 'EntityCacheUserController', - ); - // If the $enabled param is past, remove modules from the array if they're - // not enabled. - if ($enabled) { - if (!module_exists('comment')) { - unset($return['comment']); - } - if (!module_exists('taxonomy')) { - unset($return['taxonomy_term']); - unset($return['taxonomy_vocabulary']); - } - $return = array_diff_key($return, drupal_map_assoc(variable_get('entitycache_disabled_entity_types', array()))); - } - return $return; -} - -/** - * Implements hook_entity_insert(). - */ -function entitycache_entity_insert($entity, $type) { - // It is possible for other _insert() hooks to load an entity before it has - // been properly saved, for example file_field_insert(). This may cause - // an incomplete entity to be cached, since hooks which run after the one - // loading the entity do not have a chance to run. Therefore ensure the cache - // is always cleared when inserting new entities. - // Since hook_entity_insert() runs last, there's a good chance of acting - // after other modules are finished loading. - $info = entity_get_info($type); - list($id) = entity_extract_ids($type, $entity); - if (!empty($info['entity cache']) && empty($entity->migrate)) { - // file_field_insert() no longer exists. Don't take this out - // just yet though because other modules might also misbehave. - // cache_clear_all($id, 'cache_entity_' . $type); - } -} - -/** - * Implements hook_entity_delete(). - */ -function entitycache_entity_delete($entity, $type) { - $info = entity_get_info($type); - list($id) = entity_extract_ids($type, $entity); - if (!empty($info['entity cache'])) { - cache_clear_all($id, 'cache_entity_' . $type); - } -} - -/** - * Implements hook_entity_update(). - */ -function entitycache_entity_update($entity, $type) { - // It is possible for other _update() hooks to load an entity before it has - // been properly saved, for example file_field_update(). This may cause - // an incomplete entity to be cached, since hooks which run after the one - // loading the entity do not have a chance to run. Therefore ensure the cache - // is always cleared when updating entities. - // Since hook_entity_insert() runs last, there's a good chance of acting - // after other modules are finished loading. - $info = entity_get_info($type); - list($id) = entity_extract_ids($type, $entity); - if (!empty($info['entity cache']) && empty($entity->migrate)) { - cache_clear_all($id, 'cache_entity_' . $type); - } -} - -/** - * Implements hook_entitycache_node_load(). - * - * This forces book information to be added on each request, to avoid expensive - * cache clears. - */ -function book_entitycache_node_load($nodes) { - book_node_load($nodes, array()); -} - -/** - * Implements hook_entitycache_node_load(). - * - * This forces poll information to be loaded on each request, since it loads - * user-specific information during the request. - */ -function poll_entitycache_node_load($nodes) { - $polls = array(); - foreach ($nodes as $node) { - if ($node->type == 'poll') { - $polls[$node->nid] = $node; - } - } - if (!empty($polls)) { - poll_load($polls); - } -} - -/** - * Implements hook_comment_publish(). - * - * @todo: core should not call this hook outside of a comment_save(). - */ -function entitycache_comment_publish($comment) { - if (empty($comment->migrate)) { - cache_clear_all($comment->cid, 'cache_entity_comment'); - cache_clear_all($comment->nid, 'cache_entity_node'); - } -} - -/** - * Implements hook_comment_unpublish(). - * - * @todo: core should not call this hook outside of a comment_save(). - */ -function entitycache_comment_unpublish($comment) { - if (empty($comment->migrate)) { - cache_clear_all($comment->cid, 'cache_entity_comment'); - cache_clear_all($comment->nid, 'cache_entity_node'); - } -} - -/** - * Implements hook_comment_insert(). - */ -function entitycache_comment_insert($comment) { - cache_clear_all($comment->nid, 'cache_entity_node'); -} - -/** - * Implements hook_comment_update(). - */ -function entitycache_comment_update($comment) { - cache_clear_all($comment->nid, 'cache_entity_node'); -} - -/** - * Implements hook_entitycache_ENTITY_TYPE_load(). - */ -function entitycache_entitycache_comment_load($comments) { - foreach ($comments as $comment) { - $comment->new = $comment->new = node_mark($comment->nid, $comment->changed); - } -} - -/** - * Implements hook_comment_update(). - */ -function entitycache_comment_delete($comment) { - cache_clear_all($comment->nid, 'cache_entity_node'); -} - -/** - * Implements hook_user_cancel(). - */ -function entitycache_user_cancel($edit, $account, $method) { - cache_clear_all($account->uid, 'cache_entity_user'); -} - -/** - * Implements hook_user_logout(). - */ -function entitycache_user_logout($account) { - cache_clear_all($account->uid, 'cache_entity_user'); -} - -/** - * Implements hook_user_login(). - */ -function entitycache_user_login(&$edit, $account) { - cache_clear_all($account->uid, 'cache_entity_user'); -} diff --git a/entitycache.info b/entitycache.info index f4155fb..b978323 100644 --- a/entitycache.info +++ b/entitycache.info @@ -5,6 +5,10 @@ core = 7.x package = Performance and scalability files[] = entitycache.module +files[] = entitycache.entitycachecontrollerhelper.inc files[] = entitycache.comment.inc +files[] = entitycache.defaultentitycontroller.inc +files[] = entitycache.node.inc files[] = entitycache.taxonomy.inc +files[] = entitycache.user.inc files[] = entitycache.test diff --git a/entitycache.module b/entitycache.module index 7a49c0f..9ed02a2 100644 --- a/entitycache.module +++ b/entitycache.module @@ -16,237 +16,7 @@ function entitycache_entity_info_alter(&$entity_info) { } } -/** - * Entity cache helper. - * - * Note: while this class is not a real entity controller it needs to extend - * DrupalDefaultEntityController to get access to protected properties. - */ -class EntityCacheControllerHelper extends DrupalDefaultEntityController { - - public static function resetEntityCache($controller, array $ids = NULL) { - // 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()) { - $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 static::entityCacheLoad($controller, $entity_ids, array($controller->revisionKey => $revision_id)); - } else { - return static::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); - } - } - } - - // 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; - } - - public static function entityCacheGet($controller, $ids, $conditions = array()) { - $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) { - foreach ($entities as $id => $item) { - cache_set($id, $item, 'cache_entity_' . $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); - } - } -} - -/** - * Default entity controller with persistent cache. - */ -class EntityCacheDefaultEntityController extends DrupalDefaultEntityController { - public function resetCache(array $ids = NULL) { - EntityCacheControllerHelper::resetEntityCache($this, $ids); - parent::resetCache($ids); - } - public function load($ids = array(), $conditions = array()) { - return EntityCacheControllerHelper::entityCacheLoad($this, $ids, $conditions); - } -} /** * Node entity controller with persistent cache. diff --git a/entitycache.node.inc b/entitycache.node.inc new file mode 100644 index 0000000..b2ca642 --- /dev/null +++ b/entitycache.node.inc @@ -0,0 +1,19 @@ +