diff --git a/core/core.services.yml b/core/core.services.yml index e4d2ece..0e8486f 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -6,7 +6,7 @@ services: - [setContainer, ['@service_container']] cache.backend.database: class: Drupal\Core\Cache\DatabaseBackendFactory - arguments: ['@database'] + arguments: ['@database', '@cache.tag.database'] cache.backend.memory: class: Drupal\Core\Cache\MemoryBackendFactory cache.bootstrap: @@ -58,6 +58,9 @@ services: factory_method: get factory_service: cache_factory arguments: [path] + cache.tag.database: + class: Drupal\Core\Cache\DatabaseTag + arguments: ['@database'] config.cachedstorage.storage: class: Drupal\Core\Config\FileStorage factory_class: Drupal\Core\Config\FileStorageFactory diff --git a/core/lib/Drupal/Core/Cache/CacheTagBase.php b/core/lib/Drupal/Core/Cache/CacheTagBase.php new file mode 100644 index 0000000..94ffb42 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/CacheTagBase.php @@ -0,0 +1,71 @@ + $values) { + if (is_array($values)) { + foreach ($values as $value) { + $flat_tags[] = "$namespace:$value"; + } + } + else { + $flat_tags[] = "$namespace:$values"; + } + } + return $flat_tags; + } + + /** + * Act on an exception when cache might be stale. + * + * If the cache_tags table does not yet exist, that's fine but if the table + * exists and yet the query failed, then the cache is stale and the + * exception needs to propagate. + * + * @param $e + * The exception. + * @param string|null $table_name + * The table name, defaults to $this->bin. Can be cache_tags. + */ + protected function catchException(\Exception $e, $table_name = NULL) { + if ($this->connection->schema()->tableExists($table_name ?: $this->bin)) { + throw $e; + } + } + +} diff --git a/core/lib/Drupal/Core/Cache/CacheTagInterface.php b/core/lib/Drupal/Core/Cache/CacheTagInterface.php new file mode 100644 index 0000000..318e42e --- /dev/null +++ b/core/lib/Drupal/Core/Cache/CacheTagInterface.php @@ -0,0 +1,37 @@ +bin = $bin; + $this->connection = $connection; + $this->cacheTag = $cache_tag; } /** @@ -110,7 +120,7 @@ protected function prepareItem($cache, $allow_invalid) { $cache->tags = $cache->tags ? explode(' ', $cache->tags) : array(); - $checksum = $this->checksumTags($cache->tags); + $checksum = $this->cacheTag->checksumTags($cache->tags); // Check if deleteTags() has been called with any of the entry's tags. if ($cache->checksum_deletions != $checksum['deletions']) { @@ -164,8 +174,8 @@ public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANEN * Actually set the cache. */ protected function doSet($cid, $data, $expire, $tags) { - $flat_tags = $this->flattenTags($tags); - $checksum = $this->checksumTags($flat_tags); + $flat_tags = $this->cacheTag->flattenTags($tags); + $checksum = $this->cacheTag->checksumTags($flat_tags); $fields = array( 'serialized' => 0, 'created' => REQUEST_TIME, @@ -218,20 +228,7 @@ public function deleteMultiple(array $cids) { * Implements Drupal\Core\Cache\CacheBackendInterface::deleteTags(). */ public function deleteTags(array $tags) { - $tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache', array()); - foreach ($this->flattenTags($tags) as $tag) { - unset($tag_cache[$tag]); - try { - $this->connection->merge('cache_tags') - ->insertFields(array('deletions' => 1)) - ->expression('deletions', 'deletions + 1') - ->key(array('tag' => $tag)) - ->execute(); - } - catch (\Exception $e) { - $this->catchException($e, 'cache_tags'); - } - } + $this->cacheTag->deleteTags($tags); } /** @@ -276,20 +273,7 @@ public function invalidateMultiple(array $cids) { * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateTags(). */ public function invalidateTags(array $tags) { - try { - $tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache', array()); - foreach ($this->flattenTags($tags) as $tag) { - unset($tag_cache[$tag]); - $this->connection->merge('cache_tags') - ->insertFields(array('invalidations' => 1)) - ->expression('invalidations', 'invalidations + 1') - ->key(array('tag' => $tag)) - ->execute(); - } - } - catch (\Exception $e) { - $this->catchException($e, 'cache_tags'); - } + $this->cacheTag->invalidateTags($tags); } /** @@ -324,70 +308,6 @@ public function garbageCollection() { } /** - * 'Flattens' a tags array into an array of strings. - * - * @param array $tags - * Associative array of tags to flatten. - * - * @return array - * An indexed array of flattened tag identifiers. - */ - protected function flattenTags(array $tags) { - if (isset($tags[0])) { - return $tags; - } - - $flat_tags = array(); - foreach ($tags as $namespace => $values) { - if (is_array($values)) { - foreach ($values as $value) { - $flat_tags[] = "$namespace:$value"; - } - } - else { - $flat_tags[] = "$namespace:$values"; - } - } - return $flat_tags; - } - - /** - * Returns the sum total of validations for a given set of tags. - * - * @param array $tags - * Array of flat tags. - * - * @return int - * Sum of all invalidations. - * - * @see Drupal\Core\Cache\DatabaseBackend::flattenTags() - */ - protected function checksumTags($flat_tags) { - $tag_cache = &drupal_static('Drupal\Core\Cache\CacheBackendInterface::tagCache', array()); - - $checksum = array( - 'invalidations' => 0, - 'deletions' => 0, - ); - - $query_tags = array_diff($flat_tags, array_keys($tag_cache)); - if ($query_tags) { - $db_tags = $this->connection->query('SELECT tag, invalidations, deletions FROM {cache_tags} WHERE tag IN (:tags)', array(':tags' => $query_tags))->fetchAllAssoc('tag', \PDO::FETCH_ASSOC); - $tag_cache += $db_tags; - - // Fill static cache with empty objects for tags not found in the database. - $tag_cache += array_fill_keys(array_diff($query_tags, array_keys($db_tags)), $checksum); - } - - foreach ($flat_tags as $tag) { - $checksum['invalidations'] += $tag_cache[$tag]['invalidations']; - $checksum['deletions'] += $tag_cache[$tag]['deletions']; - } - - return $checksum; - } - - /** * Implements Drupal\Core\Cache\CacheBackendInterface::isEmpty(). */ public function isEmpty() { diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php b/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php index e118932..a88af47 100644 --- a/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php +++ b/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php @@ -19,12 +19,20 @@ class DatabaseBackendFactory { protected $connection; /** + * The cache tag service. + * + * @var \Drupal\Core\Cache\CacheTagInterface + */ + protected $cacheTag; + + /** * Constructs the DatabaseBackendFactory object. * * @param \Drupal\Core\Database\Connection $connection */ - function __construct(Connection $connection) { + function __construct(Connection $connection, CacheTagInterface $cache_tag) { $this->connection = $connection; + $this->cacheTag = $cache_tag; } /** @@ -37,7 +45,7 @@ function __construct(Connection $connection) { * The cache backend object for the specified cache bin. */ function get($bin) { - return new DatabaseBackend($this->connection, $bin); + return new DatabaseBackend($this->connection, $this->cacheTag, $bin); } } diff --git a/core/lib/Drupal/Core/Cache/DatabaseTag.php b/core/lib/Drupal/Core/Cache/DatabaseTag.php new file mode 100644 index 0000000..4f6d99a --- /dev/null +++ b/core/lib/Drupal/Core/Cache/DatabaseTag.php @@ -0,0 +1,113 @@ +connection = $connection; + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) { + try { + foreach ($this->flattenTags($tags) as $tag) { + unset($this->tagCache[$tag]); + $this->connection->merge('cache_tags') + ->insertFields(array('invalidations' => 1)) + ->expression('invalidations', 'invalidations + 1') + ->key(array('tag' => $tag)) + ->execute(); + } + } + catch (\Exception $e) { + $this->catchException($e, 'cache_tags'); + } + } + + /** + * {@inheritdoc} + */ + public function deleteTags(array $tags) { + foreach ($this->flattenTags($tags) as $tag) { + unset($this->tagCache[$tag]); + try { + $this->connection->merge('cache_tags') + ->insertFields(array('deletions' => 1)) + ->expression('deletions', 'deletions + 1') + ->key(array('tag' => $tag)) + ->execute(); + } + catch (\Exception $e) { + $this->catchException($e, 'cache_tags'); + } + } + } + + /** + * Returns the sum total of validations for a given set of tags. + * + * @param array $tags + * Array of flat tags. + * + * @return int + * Sum of all invalidations. + * + * @see Drupal\Core\Cache\DatabaseBackend::flattenTags() + */ + public function checksumTags($flat_tags) { + $checksum = array( + 'invalidations' => 0, + 'deletions' => 0, + ); + + $query_tags = array_diff($flat_tags, array_keys($this->tagCache)); + if ($query_tags) { + $db_tags = $this->connection->query('SELECT tag, invalidations, deletions FROM {cache_tags} WHERE tag IN (:tags)', array(':tags' => $query_tags))->fetchAllAssoc('tag', \PDO::FETCH_ASSOC); + $this->tagCache += $db_tags; + + // Fill static cache with empty objects for tags not found in the database. + $this->tagCache += array_fill_keys(array_diff($query_tags, array_keys($db_tags)), $checksum); + } + + foreach ($flat_tags as $tag) { + $checksum['invalidations'] += $this->tagCache[$tag]['invalidations']; + $checksum['deletions'] += $this->tagCache[$tag]['deletions']; + } + + return $checksum; + } + +} diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/DatabaseBackendUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Cache/DatabaseBackendUnitTest.php index 3b607a5..62042cc 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Cache/DatabaseBackendUnitTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Cache/DatabaseBackendUnitTest.php @@ -37,7 +37,7 @@ public static function getInfo() { * A new DatabaseBackend object. */ protected function createCacheBackend($bin) { - return new DatabaseBackend($this->container->get('database'), $bin); + return new DatabaseBackend($this->container->get('database'), $this->container->get('cache.tag.database'), $bin); } }