entitycache.module

Tracking 7.x-1.x branch
  1. drupal
    1. 7 contributions/entitycache/entitycache.module

Allows for caching of core entities.

Functions & methods

NameDescription
book_entitycache_node_loadImplements hook_entitycache_node_load().
entitycache_comment_deleteImplements hook_comment_update().
entitycache_comment_insertImplements hook_comment_insert().
entitycache_comment_publishImplements hook_comment_publish().
entitycache_comment_unpublishImplements hook_comment_unpublish().
entitycache_comment_updateImplements hook_comment_update().
entitycache_entitycache_comment_loadImplements hook_entitycache_ENTITY_TYPE_load().
entitycache_entity_deleteImplements hook_entity_delete().
entitycache_entity_info_alterImplements hook_entity_info_alter().
entitycache_entity_insertImplements hook_entity_insert().
entitycache_entity_updateImplements hook_entity_update().
entitycache_flush_cachesImplements hook_flush_caches().
entitycache_supported_core_entitiesHelper function to list all supported core entities.
entitycache_user_cancelImplements hook_user_cancel().
entitycache_user_loginImplements hook_user_login().
entitycache_user_logoutImplements hook_user_logout().
poll_entitycache_node_loadImplements hook_entitycache_node_load().

Classes

NameDescription
EntityCacheControllerHelperEntity cache helper.
EntityCacheDefaultEntityControllerDefault entity controller with persistent cache.
EntityCacheNodeControllerNode entity controller with persistent cache.
EntityCacheUserControllerUser entity controller with persistent cache.

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * Allows for caching of core entities.
  5. */
  6. /**
  7. * Implements hook_entity_info_alter().
  8. */
  9. function entitycache_entity_info_alter(&$entity_info) {
  10. foreach (entitycache_supported_core_entities(TRUE) as $type => $controller) {
  11. $entity_info[$type]['field cache'] = FALSE;
  12. $entity_info[$type]['entity cache'] = TRUE;
  13. $entity_info[$type]['controller class'] = $controller;
  14. }
  15. }
  16. /**
  17. * Entity cache helper.
  18. *
  19. * Note: while this class is not a real entity controller it needs to extend
  20. * DrupalDefaultEntityController to get access to protected properties.
  21. */
  22. class EntityCacheControllerHelper extends DrupalDefaultEntityController {
  23. public static function resetEntityCache($controller, array $ids = NULL) {
  24. // Reset the persistent cache.
  25. if (!empty($ids)) {
  26. cache_clear_all($ids, 'cache_entity_' . $controller->entityType);
  27. }
  28. else {
  29. // Force all cached entries to be deleted.
  30. cache_clear_all('*', 'cache_entity_' . $controller->entityType, TRUE);
  31. }
  32. // Give modules the chance to act on any entity.
  33. foreach (module_implements('entitycache_reset') as $module) {
  34. $function = $module . '_entitycache_reset';
  35. $function($ids, $controller->entityType);
  36. }
  37. // Give modules the chance to act on a specific entity type.
  38. foreach (module_implements('entitycache_' . $controller->entityType . '_reset') as $module) {
  39. $function = $module . '_entitycache_' . $controller->entityType . '_reset';
  40. $function($ids);
  41. }
  42. }
  43. public static function entityCacheLoad($controller, $ids = array(), $conditions = array()) {
  44. $entities = array();
  45. $cached_entities = array();
  46. $queried_entities = array();
  47. // Revisions are not statically cached, and require a different query to
  48. // other conditions, so separate the revision id into its own variable.
  49. if ($controller->revisionKey && isset($conditions[$controller->revisionKey])) {
  50. $revision_id = $conditions[$controller->revisionKey];
  51. unset($conditions[$controller->revisionKey]);
  52. }
  53. else {
  54. $revision_id = FALSE;
  55. }
  56. // Create a new variable which is either a prepared version of the $ids
  57. // array for later comparison with the entity cache, or FALSE if no $ids
  58. // were passed. The $ids array is reduced as items are loaded from cache,
  59. // and we need to know if it's empty for this reason to avoid querying the
  60. // database when all requested entities are loaded from cache.
  61. $passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
  62. // Try to load entities from the static cache, if the entity type supports
  63. // static caching.
  64. if ($controller->cache && !$revision_id) {
  65. $entities += $controller->cacheGet($ids, $conditions);
  66. // If any entities were loaded, remove them from the ids still to load.
  67. if ($passed_ids) {
  68. $ids = array_keys(array_diff_key($passed_ids, $entities));
  69. }
  70. }
  71. if (!empty($controller->entityInfo['entity cache']) && !$revision_id && $ids && !$conditions) {
  72. $entities += $cached_entities = self::entityCacheGet($controller, $ids, $conditions);
  73. // If any entities were loaded, remove them from the ids still to load.
  74. $ids = array_diff($ids, array_keys($cached_entities));
  75. if ($controller->cache) {
  76. // Add entities to the cache if we are not loading a revision.
  77. if (!empty($cached_entities) && !$revision_id) {
  78. $controller->cacheSet($cached_entities);
  79. }
  80. }
  81. }
  82. // Load any remaining entities from the database. This is the case if $ids
  83. // is set to FALSE (so we load all entities), if there are any ids left to
  84. // load, if loading a revision, or if $conditions was passed without $ids.
  85. if ($ids === FALSE || $ids || $revision_id || ($conditions && !$passed_ids)) {
  86. // Build the query.
  87. $query = $controller->buildQuery($ids, $conditions, $revision_id);
  88. $queried_entities = $query
  89. ->execute()
  90. ->fetchAllAssoc($controller->idKey);
  91. }
  92. // Pass all entities loaded from the database through $controller->attachLoad(),
  93. // which attaches fields (if supported by the entity type) and calls the
  94. // entity type specific load callback, for example hook_node_load().
  95. if (!empty($queried_entities)) {
  96. $controller->attachLoad($queried_entities, $revision_id);
  97. $entities += $queried_entities;
  98. }
  99. if (!empty($controller->entityInfo['entity cache'])) {
  100. // Add entities to the entity cache if we are not loading a revision.
  101. if (!empty($queried_entities) && !$revision_id) {
  102. self::entityCacheSet($controller, $queried_entities);
  103. }
  104. }
  105. if ($controller->cache) {
  106. // Add entities to the cache if we are not loading a revision.
  107. if (!empty($queried_entities) && !$revision_id) {
  108. $controller->cacheSet($queried_entities);
  109. }
  110. }
  111. // Ensure that the returned array is ordered the same as the original
  112. // $ids array if this was passed in and remove any invalid ids.
  113. if ($passed_ids) {
  114. // Remove any invalid ids from the array.
  115. $passed_ids = array_intersect_key($passed_ids, $entities);
  116. foreach ($entities as $entity) {
  117. $passed_ids[$entity->{$controller->idKey}] = $entity;
  118. }
  119. $entities = $passed_ids;
  120. }
  121. return $entities;
  122. }
  123. public static function entityCacheGet($controller, $ids, $conditions = array()) {
  124. $cached_entities = array();
  125. if ($ids && !$conditions) {
  126. $cached = cache_get_multiple($ids, 'cache_entity_' . $controller->entityType);
  127. if ($cached) {
  128. foreach ($cached as $item) {
  129. $cached_entities[$item->cid] = $item->data;
  130. }
  131. self::entityCacheAttachLoad($controller, $cached_entities);
  132. }
  133. }
  134. return $cached_entities;
  135. }
  136. public static function entityCacheSet($controller, $entities) {
  137. foreach ($entities as $item) {
  138. cache_set($item->{$controller->idKey}, $item, 'cache_entity_' . $controller->entityType);
  139. }
  140. }
  141. /**
  142. * Allow modules to implement uncached entity hooks.
  143. *
  144. * Perform two additional hook invocations for modules needing to add
  145. * uncacheable data to objects while serving the request.
  146. *
  147. * @see entitycache_entitycache_node_load()
  148. */
  149. public static function entityCacheAttachLoad($controller, $entities) {
  150. // Give modules the chance to act on any entity.
  151. foreach (module_implements('entitycache_load') as $module) {
  152. $function = $module . '_entitycache_load';
  153. $function($entities, $controller->entityType);
  154. }
  155. // Give modules the chance to act on a specific entity type.
  156. foreach (module_implements('entitycache_' . $controller->entityType . '_load') as $module) {
  157. $function = $module . '_entitycache_' . $controller->entityType . '_load';
  158. $function($entities);
  159. }
  160. }
  161. }
  162. /**
  163. * Default entity controller with persistent cache.
  164. */
  165. class EntityCacheDefaultEntityController extends DrupalDefaultEntityController {
  166. public function resetCache(array $ids = NULL) {
  167. EntityCacheControllerHelper::resetEntityCache($this, $ids);
  168. parent::resetCache($ids);
  169. }
  170. public function load($ids = array(), $conditions = array()) {
  171. return EntityCacheControllerHelper::entityCacheLoad($this, $ids, $conditions);
  172. }
  173. }
  174. /**
  175. * Node entity controller with persistent cache.
  176. */
  177. class EntityCacheNodeController extends NodeController {
  178. public function resetCache(array $ids = NULL) {
  179. EntityCacheControllerHelper::resetEntityCache($this, $ids);
  180. parent::resetCache($ids);
  181. }
  182. public function load($ids = array(), $conditions = array()) {
  183. return EntityCacheControllerHelper::entityCacheLoad($this, $ids, $conditions);
  184. }
  185. }
  186. /**
  187. * Implements hook_flush_caches().
  188. */
  189. function entitycache_flush_caches() {
  190. $bins = array();
  191. $entities = entitycache_supported_core_entities(TRUE);
  192. foreach (array_keys($entities) as $type) {
  193. $bins[] = 'cache_entity_' . $type;
  194. }
  195. return $bins;
  196. }
  197. /**
  198. * User entity controller with persistent cache.
  199. */
  200. class EntityCacheUserController extends UserController {
  201. public function resetCache(array $ids = NULL) {
  202. EntityCacheControllerHelper::resetEntityCache($this, $ids);
  203. parent::resetCache($ids);
  204. }
  205. public function load($ids = array(), $conditions = array()) {
  206. return EntityCacheControllerHelper::entityCacheLoad($this, $ids, $conditions);
  207. }
  208. }
  209. /**
  210. * Helper function to list all supported core entities.
  211. *
  212. * @param $enabled
  213. * If set, only return enabled modules.
  214. *
  215. * @return
  216. * An array of core entities.
  217. */
  218. function entitycache_supported_core_entities($enabled = FALSE) {
  219. $return = array(
  220. 'comment' => 'EntityCacheCommentController',
  221. 'file' => 'EntityCacheDefaultEntityController',
  222. 'node' => 'EntityCacheNodeController',
  223. 'taxonomy_term' => 'EntityCacheTaxonomyTermController',
  224. 'taxonomy_vocabulary' => 'EntityCacheTaxonomyVocabularyController',
  225. 'user' => 'EntityCacheUserController',
  226. );
  227. // If the $enabled param is past, remove modules from the array if they're
  228. // not enabled.
  229. if ($enabled) {
  230. if (!module_exists('comment')) {
  231. unset($return['comment']);
  232. }
  233. if (!module_exists('taxonomy')) {
  234. unset($return['taxonomy_term']);
  235. unset($return['taxonomy_vocabulary']);
  236. }
  237. $return = array_diff_key($return, drupal_map_assoc(variable_get('entitycache_disabled_entity_types', array())));
  238. }
  239. return $return;
  240. }
  241. /**
  242. * Implements hook_entity_insert().
  243. */
  244. function entitycache_entity_insert($entity, $type) {
  245. // It is possible for other _insert() hooks to load an entity before it has
  246. // been properly saved, for example file_field_insert(). This may cause
  247. // an incomplete entity to be cached, since hooks which run after the one
  248. // loading the entity do not have a chance to run. Therefore ensure the cache
  249. // is always cleared when inserting new entities.
  250. // Since hook_entity_insert() runs last, there's a good chance of acting
  251. // after other modules are finished loading.
  252. $info = entity_get_info($type);
  253. list($id) = entity_extract_ids($type, $entity);
  254. if (!empty($info['entity cache']) && empty($entity->migrate)) {
  255. // file_field_insert() no longer exists. Don't take this out
  256. // just yet though because other modules might also misbehave.
  257. // cache_clear_all($id, 'cache_entity_' . $type);
  258. }
  259. }
  260. /**
  261. * Implements hook_entity_delete().
  262. */
  263. function entitycache_entity_delete($entity, $type) {
  264. $info = entity_get_info($type);
  265. list($id) = entity_extract_ids($type, $entity);
  266. if (!empty($info['entity cache'])) {
  267. cache_clear_all($id, 'cache_entity_' . $type);
  268. }
  269. }
  270. /**
  271. * Implements hook_entity_update().
  272. */
  273. function entitycache_entity_update($entity, $type) {
  274. // It is possible for other _update() hooks to load an entity before it has
  275. // been properly saved, for example file_field_update(). This may cause
  276. // an incomplete entity to be cached, since hooks which run after the one
  277. // loading the entity do not have a chance to run. Therefore ensure the cache
  278. // is always cleared when updating entities.
  279. // Since hook_entity_insert() runs last, there's a good chance of acting
  280. // after other modules are finished loading.
  281. $info = entity_get_info($type);
  282. list($id) = entity_extract_ids($type, $entity);
  283. if (!empty($info['entity cache']) && empty($entity->migrate)) {
  284. cache_clear_all($id, 'cache_entity_' . $type);
  285. }
  286. }
  287. /**
  288. * Implements hook_entitycache_node_load().
  289. *
  290. * This forces book information to be added on each request, to avoid expensive
  291. * cache clears.
  292. */
  293. function book_entitycache_node_load($nodes) {
  294. book_node_load($nodes, array());
  295. }
  296. /**
  297. * Implements hook_entitycache_node_load().
  298. *
  299. * This forces poll information to be loaded on each request, since it loads
  300. * user-specific information during the request.
  301. */
  302. function poll_entitycache_node_load($nodes) {
  303. $polls = array();
  304. foreach ($nodes as $node) {
  305. if ($node->type == 'poll') {
  306. $polls[$node->nid] = $node;
  307. }
  308. }
  309. if (!empty($polls)) {
  310. poll_load($polls);
  311. }
  312. }
  313. /**
  314. * Implements hook_comment_publish().
  315. *
  316. * @todo: core should not call this hook outside of a comment_save().
  317. */
  318. function entitycache_comment_publish($comment) {
  319. if (empty($comment->migrate)) {
  320. cache_clear_all($comment->cid, 'cache_entity_comment');
  321. cache_clear_all($comment->nid, 'cache_entity_node');
  322. }
  323. }
  324. /**
  325. * Implements hook_comment_unpublish().
  326. *
  327. * @todo: core should not call this hook outside of a comment_save().
  328. */
  329. function entitycache_comment_unpublish($comment) {
  330. if (empty($comment->migrate)) {
  331. cache_clear_all($comment->cid, 'cache_entity_comment');
  332. cache_clear_all($comment->nid, 'cache_entity_node');
  333. }
  334. }
  335. /**
  336. * Implements hook_comment_insert().
  337. */
  338. function entitycache_comment_insert($comment) {
  339. cache_clear_all($comment->nid, 'cache_entity_node');
  340. }
  341. /**
  342. * Implements hook_comment_update().
  343. */
  344. function entitycache_comment_update($comment) {
  345. cache_clear_all($comment->nid, 'cache_entity_node');
  346. }
  347. /**
  348. * Implements hook_entitycache_ENTITY_TYPE_load().
  349. */
  350. function entitycache_entitycache_comment_load($comments) {
  351. foreach ($comments as $comment) {
  352. $comment->new = $comment->new = node_mark($comment->nid, $comment->changed);
  353. }
  354. }
  355. /**
  356. * Implements hook_comment_update().
  357. */
  358. function entitycache_comment_delete($comment) {
  359. cache_clear_all($comment->nid, 'cache_entity_node');
  360. }
  361. /**
  362. * Implements hook_user_cancel().
  363. */
  364. function entitycache_user_cancel($edit, $account, $method) {
  365. cache_clear_all($account->uid, 'cache_entity_user');
  366. }
  367. /**
  368. * Implements hook_user_logout().
  369. */
  370. function entitycache_user_logout($account) {
  371. cache_clear_all($account->uid, 'cache_entity_user');
  372. }
  373. /**
  374. * Implements hook_user_login().
  375. */
  376. function entitycache_user_login(&$edit, $account) {
  377. cache_clear_all($account->uid, 'cache_entity_user');
  378. }