PageRenderTime 25ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/core/modules/views/src/Plugin/views/cache/CachePluginBase.php

https://gitlab.com/reasonat/test8
PHP | 364 lines | 141 code | 40 blank | 183 comment | 9 complexity | 8e3b830ee16b20d567c1706183632b7f MD5 | raw file
  1. <?php
  2. namespace Drupal\views\Plugin\views\cache;
  3. use Drupal\Core\Cache\Cache;
  4. use Drupal\Core\Cache\CacheableMetadata;
  5. use Drupal\views\Plugin\views\PluginBase;
  6. use Drupal\Core\Database\Query\Select;
  7. use Drupal\views\ResultRow;
  8. /**
  9. * @defgroup views_cache_plugins Views cache plugins
  10. * @{
  11. * Plugins to handle Views caches.
  12. *
  13. * Cache plugins control how caching is done in Views.
  14. *
  15. * Cache plugins extend \Drupal\views\Plugin\views\cache\CachePluginBase.
  16. * They must be annotated with \Drupal\views\Annotation\ViewsCache
  17. * annotation, and must be in namespace directory Plugin\views\cache.
  18. *
  19. * @ingroup views_plugins
  20. * @see plugin_api
  21. */
  22. /**
  23. * The base plugin to handle caching.
  24. */
  25. abstract class CachePluginBase extends PluginBase {
  26. /**
  27. * Contains all data that should be written/read from cache.
  28. */
  29. public $storage = array();
  30. /**
  31. * Which cache bin to store query results in.
  32. *
  33. * @var string
  34. */
  35. protected $resultsBin = 'data';
  36. /**
  37. * Stores the cache ID used for the results cache.
  38. *
  39. * The cache ID is stored in generateResultsKey() got executed.
  40. *
  41. * @var string
  42. *
  43. * @see \Drupal\views\Plugin\views\cache\CachePluginBase::generateResultsKey()
  44. */
  45. protected $resultsKey;
  46. /**
  47. * Returns the resultsKey property.
  48. *
  49. * @return string
  50. * The resultsKey property.
  51. */
  52. public function getResultsKey() {
  53. return $this->resultsKey;
  54. }
  55. /**
  56. * Return a string to display as the clickable title for the
  57. * access control.
  58. */
  59. public function summaryTitle() {
  60. return $this->t('Unknown');
  61. }
  62. /**
  63. * Determine the expiration time of the cache type, or NULL if no expire.
  64. *
  65. * Plugins must override this to implement expiration.
  66. *
  67. * @param $type
  68. * The cache type, either 'query', 'result'.
  69. */
  70. protected function cacheExpire($type) {
  71. }
  72. /**
  73. * Determine expiration time in the cache table of the cache type
  74. * or CACHE_PERMANENT if item shouldn't be removed automatically from cache.
  75. *
  76. * Plugins must override this to implement expiration in the cache table.
  77. *
  78. * @param $type
  79. * The cache type, either 'query', 'result'.
  80. */
  81. protected function cacheSetMaxAge($type) {
  82. return Cache::PERMANENT;
  83. }
  84. /**
  85. * Save data to the cache.
  86. *
  87. * A plugin should override this to provide specialized caching behavior.
  88. */
  89. public function cacheSet($type) {
  90. switch ($type) {
  91. case 'query':
  92. // Not supported currently, but this is certainly where we'd put it.
  93. break;
  94. case 'results':
  95. $data = array(
  96. 'result' => $this->prepareViewResult($this->view->result),
  97. 'total_rows' => isset($this->view->total_rows) ? $this->view->total_rows : 0,
  98. 'current_page' => $this->view->getCurrentPage(),
  99. );
  100. $expire = ($this->cacheSetMaxAge($type) === Cache::PERMANENT) ? Cache::PERMANENT : (int) $this->view->getRequest()->server->get('REQUEST_TIME') + $this->cacheSetMaxAge($type);
  101. \Drupal::cache($this->resultsBin)->set($this->generateResultsKey(), $data, $expire, $this->getCacheTags());
  102. break;
  103. }
  104. }
  105. /**
  106. * Retrieve data from the cache.
  107. *
  108. * A plugin should override this to provide specialized caching behavior.
  109. */
  110. public function cacheGet($type) {
  111. $cutoff = $this->cacheExpire($type);
  112. switch ($type) {
  113. case 'query':
  114. // Not supported currently, but this is certainly where we'd put it.
  115. return FALSE;
  116. case 'results':
  117. // Values to set: $view->result, $view->total_rows, $view->execute_time,
  118. // $view->current_page.
  119. if ($cache = \Drupal::cache($this->resultsBin)->get($this->generateResultsKey())) {
  120. if (!$cutoff || $cache->created > $cutoff) {
  121. $this->view->result = $cache->data['result'];
  122. // Load entities for each result.
  123. $this->view->query->loadEntities($this->view->result);
  124. $this->view->total_rows = $cache->data['total_rows'];
  125. $this->view->setCurrentPage($cache->data['current_page'], TRUE);
  126. $this->view->execute_time = 0;
  127. return TRUE;
  128. }
  129. }
  130. return FALSE;
  131. }
  132. }
  133. /**
  134. * Clear out cached data for a view.
  135. */
  136. public function cacheFlush() {
  137. Cache::invalidateTags($this->view->storage->getCacheTagsToInvalidate());
  138. }
  139. /**
  140. * Post process any rendered data.
  141. *
  142. * This can be valuable to be able to cache a view and still have some level of
  143. * dynamic output. In an ideal world, the actual output will include HTML
  144. * comment based tokens, and then the post process can replace those tokens.
  145. *
  146. * Example usage. If it is known that the view is a node view and that the
  147. * primary field will be a nid, you can do something like this:
  148. *
  149. * <!--post-FIELD-NID-->
  150. *
  151. * And then in the post render, create an array with the text that should
  152. * go there:
  153. *
  154. * strtr($output, array('<!--post-FIELD-1-->', 'output for FIELD of nid 1');
  155. *
  156. * All of the cached result data will be available in $view->result, as well,
  157. * so all ids used in the query should be discoverable.
  158. */
  159. public function postRender(&$output) { }
  160. /**
  161. * Calculates and sets a cache ID used for the result cache.
  162. *
  163. * @return string
  164. * The generated cache ID.
  165. */
  166. public function generateResultsKey() {
  167. if (!isset($this->resultsKey)) {
  168. $build_info = $this->view->build_info;
  169. foreach (array('query', 'count_query') as $index) {
  170. // If the default query back-end is used generate SQL query strings from
  171. // the query objects.
  172. if ($build_info[$index] instanceof Select) {
  173. $query = clone $build_info[$index];
  174. $query->preExecute();
  175. $build_info[$index] = array(
  176. 'query' => (string)$query,
  177. 'arguments' => $query->getArguments(),
  178. );
  179. }
  180. }
  181. $key_data = [
  182. 'build_info' => $build_info,
  183. ];
  184. // @todo https://www.drupal.org/node/2433591 might solve it to not require
  185. // the pager information here.
  186. $key_data['pager'] = [
  187. 'page' => $this->view->getCurrentPage(),
  188. 'items_per_page' => $this->view->getItemsPerPage(),
  189. 'offset' => $this->view->getOffset(),
  190. ];
  191. $key_data += \Drupal::service('cache_contexts_manager')->convertTokensToKeys($this->displayHandler->getCacheMetadata()->getCacheContexts())->getKeys();
  192. $this->resultsKey = $this->view->storage->id() . ':' . $this->displayHandler->display['id'] . ':results:' . hash('sha256', serialize($key_data));
  193. }
  194. return $this->resultsKey;
  195. }
  196. /**
  197. * Gets an array of cache tags for the current view.
  198. *
  199. * @return string[]
  200. * An array of cache tags based on the current view.
  201. */
  202. public function getCacheTags() {
  203. $tags = $this->view->storage->getCacheTags();
  204. // The list cache tags for the entity types listed in this view.
  205. $entity_information = $this->view->getQuery()->getEntityTableInfo();
  206. if (!empty($entity_information)) {
  207. // Add the list cache tags for each entity type used by this view.
  208. foreach ($entity_information as $table => $metadata) {
  209. $tags = Cache::mergeTags($tags, \Drupal::entityManager()->getDefinition($metadata['entity_type'])->getListCacheTags());
  210. }
  211. }
  212. $tags = Cache::mergeTags($tags, $this->view->getQuery()->getCacheTags());
  213. return $tags;
  214. }
  215. /**
  216. * Gets the max age for the current view.
  217. *
  218. * @return int
  219. */
  220. public function getCacheMaxAge() {
  221. $max_age = $this->getDefaultCacheMaxAge();
  222. $max_age = Cache::mergeMaxAges($max_age, $this->view->getQuery()->getCacheMaxAge());
  223. return $max_age;
  224. }
  225. /**
  226. * Returns the default cache max age.
  227. */
  228. protected function getDefaultCacheMaxAge() {
  229. // The default cache backend is not caching anything.
  230. return 0;
  231. }
  232. /**
  233. * Prepares the view result before putting it into cache.
  234. *
  235. * @param \Drupal\views\ResultRow[] $result
  236. * The result containing loaded entities.
  237. *
  238. * @return \Drupal\views\ResultRow[] $result
  239. * The result without loaded entities.
  240. */
  241. protected function prepareViewResult(array $result) {
  242. $return = [];
  243. // Clone each row object and remove any loaded entities, to keep the
  244. // original result rows intact.
  245. foreach ($result as $key => $row) {
  246. $clone = clone $row;
  247. $clone->resetEntityData();
  248. $return[$key] = $clone;
  249. }
  250. return $return;
  251. }
  252. /**
  253. * Alters the cache metadata of a display upon saving a view.
  254. *
  255. * @param \Drupal\Core\Cache\CacheableMetadata $cache_metadata
  256. * The cache metadata.
  257. */
  258. public function alterCacheMetadata(CacheableMetadata $cache_metadata) {
  259. }
  260. /**
  261. * Returns the row cache tags.
  262. *
  263. * @param ResultRow $row
  264. * A result row.
  265. *
  266. * @return string[]
  267. * The row cache tags.
  268. */
  269. public function getRowCacheTags(ResultRow $row) {
  270. $tags = !empty($row->_entity) ? $row->_entity->getCacheTags() : [];
  271. if (!empty($row->_relationship_entities)) {
  272. foreach ($row->_relationship_entities as $entity) {
  273. $tags = Cache::mergeTags($tags, $entity->getCacheTags());
  274. }
  275. }
  276. return $tags;
  277. }
  278. /**
  279. * Returns the row cache keys.
  280. *
  281. * @param \Drupal\views\ResultRow $row
  282. * A result row.
  283. *
  284. * @return string[]
  285. * The row cache keys.
  286. */
  287. public function getRowCacheKeys(ResultRow $row) {
  288. return [
  289. 'views',
  290. 'fields',
  291. $this->view->id(),
  292. $this->view->current_display,
  293. $this->getRowId($row),
  294. ];
  295. }
  296. /**
  297. * Returns a unique identifier for the specified row.
  298. *
  299. * @param \Drupal\views\ResultRow $row
  300. * A result row.
  301. *
  302. * @return string
  303. * The row identifier.
  304. */
  305. public function getRowId(ResultRow $row) {
  306. // Here we compute a unique identifier for the row by computing the hash of
  307. // its data. We exclude the current index, since the same row could have a
  308. // different result index depending on the user permissions. We exclude also
  309. // entity data, since serializing entity objects is very expensive. Instead
  310. // we include entity cache tags, which are enough to identify all the
  311. // entities associated with the row.
  312. $row_data = array_diff_key((array) $row, array_flip(['index', '_entity', '_relationship_entities'])) + $this->getRowCacheTags($row);
  313. // This ensures that we get a unique identifier taking field handler access
  314. // into account: users having access to different sets of fields will get
  315. // different row identifiers.
  316. $field_ids = array_keys($this->view->field);
  317. $row_data += array_flip($field_ids);
  318. // Finally we compute a hash of row data and return it as row identifier.
  319. return hash('sha256', serialize($row_data));
  320. }
  321. }
  322. /**
  323. * @}
  324. */