EVOLUTION-NINJA
Edit File: entitycache.module
<?php /** * @file * Allows for caching of core entities. */ /** * Implements hook_entity_info_alter(). */ function entitycache_entity_info_alter(&$entity_info) { foreach (entitycache_supported_core_entities(TRUE) as $type => $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 * 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); } } 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; // 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) { self::entityCacheSet($controller, $queried_entities); } } 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 $item) { cache_set($item->{$controller->idKey}, $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. */ 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(). */ function entitycache_flush_caches() { $bins = array(); $entities = entitycache_supported_core_entities(TRUE); foreach (array_keys($entities) as $type) { $bins[] = 'cache_entity_' . $type; } 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'); }