Upgraded drupal core with security updates
[yaffs-website] / web / core / lib / Drupal / Core / Cache / DatabaseCacheTagsChecksum.php
1 <?php
2
3 namespace Drupal\Core\Cache;
4
5 use Drupal\Core\Database\Connection;
6 use Drupal\Core\Database\SchemaObjectExistsException;
7
8 /**
9  * Cache tags invalidations checksum implementation that uses the database.
10  */
11 class DatabaseCacheTagsChecksum implements CacheTagsChecksumInterface, CacheTagsInvalidatorInterface {
12
13   /**
14    * The database connection.
15    *
16    * @var \Drupal\Core\Database\Connection
17    */
18   protected $connection;
19
20   /**
21    * Contains already loaded cache invalidations from the database.
22    *
23    * @var array
24    */
25   protected $tagCache = [];
26
27   /**
28    * A list of tags that have already been invalidated in this request.
29    *
30    * Used to prevent the invalidation of the same cache tag multiple times.
31    *
32    * @var array
33    */
34   protected $invalidatedTags = [];
35
36   /**
37    * Constructs a DatabaseCacheTagsChecksum object.
38    *
39    * @param \Drupal\Core\Database\Connection $connection
40    *   The database connection.
41    */
42   public function __construct(Connection $connection) {
43     $this->connection = $connection;
44   }
45
46   /**
47    * {@inheritdoc}
48    */
49   public function invalidateTags(array $tags) {
50     try {
51       foreach ($tags as $tag) {
52         // Only invalidate tags once per request unless they are written again.
53         if (isset($this->invalidatedTags[$tag])) {
54           continue;
55         }
56         $this->invalidatedTags[$tag] = TRUE;
57         unset($this->tagCache[$tag]);
58         $this->connection->merge('cachetags')
59           ->insertFields(['invalidations' => 1])
60           ->expression('invalidations', 'invalidations + 1')
61           ->key('tag', $tag)
62           ->execute();
63       }
64     }
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
68       // created.
69       if (!$this->ensureTableExists()) {
70         $this->catchException($e);
71       }
72     }
73   }
74
75   /**
76    * {@inheritdoc}
77    */
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
82     // correctly.
83     foreach ($tags as $tag) {
84       unset($this->invalidatedTags[$tag]);
85     }
86     return $this->calculateChecksum($tags);
87   }
88
89   /**
90    * {@inheritdoc}
91    */
92   public function isValid($checksum, array $tags) {
93     return $checksum == $this->calculateChecksum($tags);
94   }
95
96   /**
97    * Calculates the current checksum for a given set of tags.
98    *
99    * @param array $tags
100    *   The array of tags to calculate the checksum for.
101    *
102    * @return int
103    *   The calculated checksum.
104    */
105   protected function calculateChecksum(array $tags) {
106     $checksum = 0;
107
108     $query_tags = array_diff($tags, array_keys($this->tagCache));
109     if ($query_tags) {
110       $db_tags = [];
111       try {
112         $db_tags = $this->connection->query('SELECT tag, invalidations FROM {cachetags} WHERE tag IN ( :tags[] )', [':tags[]' => $query_tags])
113           ->fetchAllKeyed();
114         $this->tagCache += $db_tags;
115       }
116       catch (\Exception $e) {
117         // If the table does not exist yet, create.
118         if (!$this->ensureTableExists()) {
119           $this->catchException($e);
120         }
121       }
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);
124     }
125
126     foreach ($tags as $tag) {
127       $checksum += $this->tagCache[$tag];
128     }
129
130     return $checksum;
131   }
132
133   /**
134    * {@inheritdoc}
135    */
136   public function reset() {
137     $this->tagCache = [];
138     $this->invalidatedTags = [];
139   }
140
141   /**
142    * Check if the cache tags table exists and create it if not.
143    */
144   protected function ensureTableExists() {
145     try {
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);
151
152         return TRUE;
153       }
154     }
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) {
159       return TRUE;
160     }
161     return FALSE;
162   }
163
164   /**
165    * Defines the schema for the {cachetags} table.
166    */
167   public function schemaDefinition() {
168     $schema = [
169       'description' => 'Cache table for tracking cache tag invalidations.',
170       'fields' => [
171         'tag' => [
172           'description' => 'Namespace-prefixed tag string.',
173           'type' => 'varchar_ascii',
174           'length' => 255,
175           'not null' => TRUE,
176           'default' => '',
177         ],
178         'invalidations' => [
179           'description' => 'Number incremented when the tag is invalidated.',
180           'type' => 'int',
181           'not null' => TRUE,
182           'default' => 0,
183         ],
184       ],
185       'primary key' => ['tag'],
186     ];
187     return $schema;
188   }
189
190   /**
191    * Act on an exception when cache might be stale.
192    *
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.
196    *
197    * @param \Exception $e
198    *   The exception.
199    *
200    * @throws \Exception
201    */
202   protected function catchException(\Exception $e) {
203     if ($this->connection->schema()->tableExists('cachetags')) {
204       throw $e;
205     }
206   }
207
208 }