3 namespace Drupal\Core\Cache;
5 use Drupal\Core\Database\Connection;
6 use Drupal\Core\Database\SchemaObjectExistsException;
9 * Cache tags invalidations checksum implementation that uses the database.
11 class DatabaseCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTagsInvalidatorInterface {
14 * The database connection.
16 * @var \Drupal\Core\Database\Connection
18 protected $connection;
21 * Contains already loaded cache invalidations from the database.
25 protected $tagCache = [];
28 * A list of tags that have already been invalidated in this request.
30 * Used to prevent the invalidation of the same cache tag multiple times.
34 protected $invalidatedTags = [];
37 * Constructs a DatabaseCacheTagsChecksum object.
39 * @param \Drupal\Core\Database\Connection $connection
40 * The database connection.
42 public function __construct(Connection $connection) {
43 $this->connection = $connection;
49 public function invalidateTags(array $tags) {
51 foreach ($tags as $tag) {
52 // Only invalidate tags once per request unless they are written again.
53 if (isset($this->invalidatedTags[$tag])) {
56 $this->invalidatedTags[$tag] = TRUE;
57 unset($this->tagCache[$tag]);
58 $this->connection->merge('cachetags')
59 ->insertFields(['invalidations' => 1])
60 ->expression('invalidations', 'invalidations + 1')
65 catch (\Exception $e) {
66 // Create the cache table, which will be empty. This fixes cases during
67 // core install where cache tags are invalidated before the table is
69 if (!$this->ensureTableExists()) {
70 $this->catchException($e);
78 public function getCurrentChecksum(array $tags) {
79 // Remove tags that were already invalidated during this request from the
80 // static caches so that another invalidation can occur later in the same
81 // request. Without that, written cache items would not be invalidated
83 foreach ($tags as $tag) {
84 unset($this->invalidatedTags[$tag]);
86 return $this->calculateChecksum($tags);
92 public function isValid($checksum, array $tags) {
93 return $checksum == $this->calculateChecksum($tags);
97 * Calculates the current checksum for a given set of tags.
100 * The array of tags to calculate the checksum for.
103 * The calculated checksum.
105 protected function calculateChecksum(array $tags) {
108 $query_tags = array_diff($tags, array_keys($this->tagCache));
112 $db_tags = $this->connection->query('SELECT tag, invalidations FROM {cachetags} WHERE tag IN ( :tags[] )', [':tags[]' => $query_tags])
114 $this->tagCache += $db_tags;
116 catch (\Exception $e) {
117 // If the table does not exist yet, create.
118 if (!$this->ensureTableExists()) {
119 $this->catchException($e);
122 // Fill static cache with empty objects for tags not found in the database.
123 $this->tagCache += array_fill_keys(array_diff($query_tags, array_keys($db_tags)), 0);
126 foreach ($tags as $tag) {
127 $checksum += $this->tagCache[$tag];
136 public function reset() {
137 $this->tagCache = [];
138 $this->invalidatedTags = [];
142 * Check if the cache tags table exists and create it if not.
144 protected function ensureTableExists() {
146 $database_schema = $this->connection->schema();
147 // Create the cache tags table if it does not exist.
148 if (!$database_schema->tableExists('cachetags')) {
149 $schema_definition = $this->schemaDefinition();
150 $database_schema->createTable('cachetags', $schema_definition);
155 // If another process has already created the cachetags table, attempting to
156 // recreate it will throw an exception. In this case just catch the
157 // exception and do nothing.
158 catch (SchemaObjectExistsException $e) {
165 * Defines the schema for the {cachetags} table.
167 public function schemaDefinition() {
169 'description' => 'Cache table for tracking cache tag invalidations.',
172 'description' => 'Namespace-prefixed tag string.',
173 'type' => 'varchar_ascii',
179 'description' => 'Number incremented when the tag is invalidated.',
185 'primary key' => ['tag'],
191 * Act on an exception when cache might be stale.
193 * If the {cachetags} table does not yet exist, that's fine but if the table
194 * exists and yet the query failed, then the cache is stale and the
195 * exception needs to propagate.
197 * @param \Exception $e
202 protected function catchException(\Exception $e) {
203 if ($this->connection->schema()->tableExists('cachetags')) {