doInvalidateTags($this->delayedTags); } $this->delayedTags = []; } /** * Implements \Drupal\Core\Cache\CacheTagsChecksumInterface::invalidateTags() */ public function invalidateTags(array $tags) { // Only invalidate tags once per request unless they are written again. foreach ($tags as $key => $tag) { if (isset($this->invalidatedTags[$tag])) { unset($tags[$key]); } else { $this->invalidatedTags[$tag] = TRUE; unset($this->tagCache[$tag]); } } if (!$tags) { return; } $in_transaction = $this->getDatabaseConnection()->inTransaction(); if ($in_transaction) { if (empty($this->delayedTags)) { $this->getDatabaseConnection()->addRootTransactionEndCallback([$this, 'rootTransactionEndCallback']); } $this->delayedTags = Cache::mergeTags($this->delayedTags, $tags); } else { $this->doInvalidateTags($tags); } } /** * Implements \Drupal\Core\Cache\CacheTagsChecksumInterface::getCurrentChecksum() */ public function getCurrentChecksum(array $tags) { // Any cache writes in this request containing cache tags whose invalidation // has been delayed due to an in-progress transaction must not be read by // any other request, so use a nonsensical checksum which will cause any // written cache items to be ignored. if (!empty(array_intersect($tags, $this->delayedTags))) { return CacheTagsChecksumInterface::INVALID_CHECKSUM_WHILE_IN_TRANSACTION; } // Remove tags that were already invalidated during this request from the // static caches so that another invalidation can occur later in the same // request. Without that, written cache items would not be invalidated // correctly. foreach ($tags as $tag) { unset($this->invalidatedTags[$tag]); } return $this->calculateChecksum($tags); } /** * Implements \Drupal\Core\Cache\CacheTagsChecksumInterface::isValid() */ public function isValid($checksum, array $tags) { // Any cache reads in this request involving cache tags whose invalidation // has been delayed due to an in-progress transaction are not allowed to use // data stored in cache; it must be assumed to be stale. This forces those // results to be computed instead. Together with the logic in // ::getCurrentChecksum(), it also prevents that computed data from being // written to the cache. if (!empty(array_intersect($tags, $this->delayedTags))) { return FALSE; } return $checksum == $this->calculateChecksum($tags); } /** * Calculates the current checksum for a given set of tags. * * @param string[] $tags * The array of tags to calculate the checksum for. * * @return int * The calculated checksum. */ protected function calculateChecksum(array $tags) { $checksum = 0; $query_tags = array_diff($tags, array_keys($this->tagCache)); if ($query_tags) { $tag_invalidations = $this->getTagInvalidationCounts($query_tags); $this->tagCache += $tag_invalidations; // Fill static cache with empty objects for tags not found in the storage. $this->tagCache += array_fill_keys(array_diff($query_tags, array_keys($tag_invalidations)), 0); } foreach ($tags as $tag) { $checksum += $this->tagCache[$tag]; } return $checksum; } /** * Implements \Drupal\Core\Cache\CacheTagsChecksumInterface::reset() */ public function reset() { $this->tagCache = []; $this->invalidatedTags = []; } /** * Fetches invalidation counts for cache tags. * * @param string[] $tags * The list of tags to fetch invalidations for. * * @return int[] * List of invalidation counts keyed by the respective cache tag. */ abstract protected function getTagInvalidationCounts(array $tags); /** * Returns the database connection. * * @return \Drupal\Core\Database\Connection * The database connection. */ abstract protected function getDatabaseConnection(); /** * Marks cache items with any of the specified tags as invalid. * * @param string[] $tags * The set of tags for which to invalidate cache items. */ abstract protected function doInvalidateTags(array $tags); }