CacheTagsChecksumTrait.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. <?php
  2. namespace Drupal\Core\Cache;
  3. /**
  4. * A trait for cache tag checksum implementations.
  5. *
  6. * Handles delayed cache tag invalidations.
  7. */
  8. trait CacheTagsChecksumTrait {
  9. /**
  10. * A list of tags that have already been invalidated in this request.
  11. *
  12. * Used to prevent the invalidation of the same cache tag multiple times.
  13. *
  14. * @var bool[]
  15. */
  16. protected $invalidatedTags = [];
  17. /**
  18. * The set of cache tags whose invalidation is delayed.
  19. *
  20. * @var string[]
  21. */
  22. protected $delayedTags = [];
  23. /**
  24. * Contains already loaded tag invalidation counts from the storage.
  25. *
  26. * @var int[]
  27. */
  28. protected $tagCache = [];
  29. /**
  30. * Callback to be invoked just after a database transaction gets committed.
  31. *
  32. * Executes all delayed tag invalidations.
  33. *
  34. * @param bool $success
  35. * Whether or not the transaction was successful.
  36. */
  37. public function rootTransactionEndCallback($success) {
  38. if ($success) {
  39. $this->doInvalidateTags($this->delayedTags);
  40. }
  41. $this->delayedTags = [];
  42. }
  43. /**
  44. * Implements \Drupal\Core\Cache\CacheTagsChecksumInterface::invalidateTags()
  45. */
  46. public function invalidateTags(array $tags) {
  47. // Only invalidate tags once per request unless they are written again.
  48. foreach ($tags as $key => $tag) {
  49. if (isset($this->invalidatedTags[$tag])) {
  50. unset($tags[$key]);
  51. }
  52. else {
  53. $this->invalidatedTags[$tag] = TRUE;
  54. unset($this->tagCache[$tag]);
  55. }
  56. }
  57. if (!$tags) {
  58. return;
  59. }
  60. $in_transaction = $this->getDatabaseConnection()->inTransaction();
  61. if ($in_transaction) {
  62. if (empty($this->delayedTags)) {
  63. $this->getDatabaseConnection()->addRootTransactionEndCallback([$this, 'rootTransactionEndCallback']);
  64. }
  65. $this->delayedTags = Cache::mergeTags($this->delayedTags, $tags);
  66. }
  67. else {
  68. $this->doInvalidateTags($tags);
  69. }
  70. }
  71. /**
  72. * Implements \Drupal\Core\Cache\CacheTagsChecksumInterface::getCurrentChecksum()
  73. */
  74. public function getCurrentChecksum(array $tags) {
  75. // Any cache writes in this request containing cache tags whose invalidation
  76. // has been delayed due to an in-progress transaction must not be read by
  77. // any other request, so use a nonsensical checksum which will cause any
  78. // written cache items to be ignored.
  79. if (!empty(array_intersect($tags, $this->delayedTags))) {
  80. return CacheTagsChecksumInterface::INVALID_CHECKSUM_WHILE_IN_TRANSACTION;
  81. }
  82. // Remove tags that were already invalidated during this request from the
  83. // static caches so that another invalidation can occur later in the same
  84. // request. Without that, written cache items would not be invalidated
  85. // correctly.
  86. foreach ($tags as $tag) {
  87. unset($this->invalidatedTags[$tag]);
  88. }
  89. return $this->calculateChecksum($tags);
  90. }
  91. /**
  92. * Implements \Drupal\Core\Cache\CacheTagsChecksumInterface::isValid()
  93. */
  94. public function isValid($checksum, array $tags) {
  95. // Any cache reads in this request involving cache tags whose invalidation
  96. // has been delayed due to an in-progress transaction are not allowed to use
  97. // data stored in cache; it must be assumed to be stale. This forces those
  98. // results to be computed instead. Together with the logic in
  99. // ::getCurrentChecksum(), it also prevents that computed data from being
  100. // written to the cache.
  101. if (!empty(array_intersect($tags, $this->delayedTags))) {
  102. return FALSE;
  103. }
  104. return $checksum == $this->calculateChecksum($tags);
  105. }
  106. /**
  107. * Calculates the current checksum for a given set of tags.
  108. *
  109. * @param string[] $tags
  110. * The array of tags to calculate the checksum for.
  111. *
  112. * @return int
  113. * The calculated checksum.
  114. */
  115. protected function calculateChecksum(array $tags) {
  116. $checksum = 0;
  117. $query_tags = array_diff($tags, array_keys($this->tagCache));
  118. if ($query_tags) {
  119. $tag_invalidations = $this->getTagInvalidationCounts($query_tags);
  120. $this->tagCache += $tag_invalidations;
  121. // Fill static cache with empty objects for tags not found in the storage.
  122. $this->tagCache += array_fill_keys(array_diff($query_tags, array_keys($tag_invalidations)), 0);
  123. }
  124. foreach ($tags as $tag) {
  125. $checksum += $this->tagCache[$tag];
  126. }
  127. return $checksum;
  128. }
  129. /**
  130. * Implements \Drupal\Core\Cache\CacheTagsChecksumInterface::reset()
  131. */
  132. public function reset() {
  133. $this->tagCache = [];
  134. $this->invalidatedTags = [];
  135. }
  136. /**
  137. * Fetches invalidation counts for cache tags.
  138. *
  139. * @param string[] $tags
  140. * The list of tags to fetch invalidations for.
  141. *
  142. * @return int[]
  143. * List of invalidation counts keyed by the respective cache tag.
  144. */
  145. abstract protected function getTagInvalidationCounts(array $tags);
  146. /**
  147. * Returns the database connection.
  148. *
  149. * @return \Drupal\Core\Database\Connection
  150. * The database connection.
  151. */
  152. abstract protected function getDatabaseConnection();
  153. /**
  154. * Marks cache items with any of the specified tags as invalid.
  155. *
  156. * @param string[] $tags
  157. * The set of tags for which to invalidate cache items.
  158. */
  159. abstract protected function doInvalidateTags(array $tags);
  160. }