diff --git a/core/core.services.yml b/core/core.services.yml index cf4ebab..81ec13a 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -6,7 +6,10 @@ services: - [setContainer, ['@service_container']] cache.backend.database: class: Drupal\Core\Cache\DatabaseBackendFactory - arguments: ['@database'] + arguments: ['@database', '@cache_tag_factory'] + cache.backend.memory: + class: Drupal\Core\Cache\MemoryBackendFactory + arguments: ['@cache_tag_factory'] cache.bootstrap: class: Drupal\Core\Cache\CacheBackendInterface tags: @@ -63,6 +66,22 @@ services: factory_method: get factory_service: cache_factory arguments: [path] + cache_tag_factory: + class: Drupal\Core\Cache\CacheTagBackendFactory + arguments: ['@settings'] + calls: + - [setContainer, ['@service_container']] + cache.tag.database: + class: Drupal\Core\Cache\DatabaseTagBackend + arguments: ['@database'] + tags: + - { name: cache.tag } + - { name: persist } + cache.tag.memory: + class: Drupal\Core\Cache\MemoryTagBackend + tags: + - { name: cache.tag } + - { name: persist } config.cachedstorage.storage: class: Drupal\Core\Config\FileStorage factory_class: Drupal\Core\Config\FileStorageFactory diff --git a/core/lib/Drupal/Core/Cache/BackendChain.php b/core/lib/Drupal/Core/Cache/BackendChain.php index 4d8ca05..f335d0b 100644 --- a/core/lib/Drupal/Core/Cache/BackendChain.php +++ b/core/lib/Drupal/Core/Cache/BackendChain.php @@ -150,15 +150,6 @@ public function deleteMultiple(array $cids) { } /** - * Implements Drupal\Core\Cache\CacheBackendInterface::deleteTags(). - */ - public function deleteTags(array $tags) { - foreach ($this->backends as $backend) { - $backend->deleteTags($tags); - } - } - - /** * Implements Drupal\Core\Cache\CacheBackendInterface::deleteAll(). */ public function deleteAll() { @@ -186,15 +177,6 @@ public function invalidateMultiple(array $cids) { } /** - * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateTags(). - */ - public function invalidateTags(array $tags) { - foreach ($this->backends as $backend) { - $backend->invalidateTags($tags); - } - } - - /** * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateAll(). */ public function invalidateAll() { diff --git a/core/lib/Drupal/Core/Cache/Cache.php b/core/lib/Drupal/Core/Cache/Cache.php index f224d14..57f4f54 100644 --- a/core/lib/Drupal/Core/Cache/Cache.php +++ b/core/lib/Drupal/Core/Cache/Cache.php @@ -33,8 +33,8 @@ class Cache { * The list of tags to delete cache items for. */ public static function deleteTags(array $tags) { - foreach (static::getBins() as $cache_backend) { - $cache_backend->deleteTags($tags); + foreach (static::getTags() as $tag_service) { + $tag_service->deleteTags($tags); } } @@ -52,16 +52,32 @@ public static function deleteTags(array $tags) { * The list of tags to invalidate cache items for. */ public static function invalidateTags(array $tags) { - foreach (static::getBins() as $cache_backend) { - $cache_backend->invalidateTags($tags); + foreach (static::getTags() as $tag_service) { + $tag_service->invalidateTags($tags); } } /** + * Gets all cache tag services. + * + * @return array + * An array of cache tag objects keyed by service name. + */ + public static function getTags() { + $tags = array(); + $container = \Drupal::getContainer(); + foreach ($container->getParameter('cache_tags') as $service_id => $tag) { + $tags[$tag] = $container->get($service_id); + } + + return $tags; + } + + /** * Gets all cache bin services. * * @return array - * An array of cache backend objects keyed by cache bins. + * An array of cache bin objects keyed by cache bin. */ public static function getBins() { $bins = array(); diff --git a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php index 0208b77..9651394 100644 --- a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php +++ b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php @@ -108,7 +108,7 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array * * @see \Drupal\Core\Cache\CacheBackendInterface::invalidate() * @see \Drupal\Core\Cache\CacheBackendInterface::deleteMultiple() - * @see \Drupal\Core\Cache\CacheBackendInterface::deleteTags() + * @see \Drupal\Core\Cache\CacheTagBackendInterface::deleteTags() * @see \Drupal\Core\Cache\CacheBackendInterface::deleteAll() */ public function delete($cid); @@ -127,39 +127,18 @@ public function delete($cid); * * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple() * @see \Drupal\Core\Cache\CacheBackendInterface::delete() - * @see \Drupal\Core\Cache\CacheBackendInterface::deleteTags() + * @see \Drupal\Core\Cache\CacheTagBackendInterface::deleteTags() * @see \Drupal\Core\Cache\CacheBackendInterface::deleteAll() */ public function deleteMultiple(array $cids); /** - * Deletes items with any of the specified tags. - * - * If the cache items are being deleted because they are no longer "fresh", - * you may consider using invalidateTags() instead. This allows callers to - * retrieve the invalid items by calling get() with $allow_invalid set to TRUE. - * In some cases an invalid item may be acceptable rather than having to - * rebuild the cache. - * - * @param array $tags - * Associative array of tags, in the same format that is passed to - * CacheBackendInterface::set(). - * - * @see \Drupal\Core\Cache\CacheBackendInterface::set() - * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateTags() - * @see \Drupal\Core\Cache\CacheBackendInterface::delete() - * @see \Drupal\Core\Cache\CacheBackendInterface::deleteMultiple() - * @see \Drupal\Core\Cache\CacheBackendInterface::deleteAll() - */ - public function deleteTags(array $tags); - - /** * Deletes all cache items in a bin. * * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateAll() * @see \Drupal\Core\Cache\CacheBackendInterface::delete() * @see \Drupal\Core\Cache\CacheBackendInterface::deleteMultiple() - * @see \Drupal\Core\Cache\CacheBackendInterface::deleteTags() + * @see \Drupal\Core\Cache\CacheTagBackendInterface::deleteTags() */ public function deleteAll(); @@ -174,7 +153,7 @@ public function deleteAll(); * * @see \Drupal\Core\Cache\CacheBackendInterface::delete() * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple() - * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateTags() + * @see \Drupal\Core\Cache\CacheTagBackendInterface::invalidateTags() * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateAll() */ public function invalidate($cid); @@ -190,27 +169,12 @@ public function invalidate($cid); * * @see \Drupal\Core\Cache\CacheBackendInterface::deleteMultiple() * @see \Drupal\Core\Cache\CacheBackendInterface::invalidate() - * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateTags() + * @see \Drupal\Core\Cache\CacheTagBackendInterface::invalidateTags() * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateAll() */ public function invalidateMultiple(array $cids); /** - * Marks cache items with any of the specified tags as invalid. - * - * @param array $tags - * Associative array of tags, in the same format that is passed to - * CacheBackendInterface::set(). - * - * @see \Drupal\Core\Cache\CacheBackendInterface::set() - * @see \Drupal\Core\Cache\CacheBackendInterface::deleteTags() - * @see \Drupal\Core\Cache\CacheBackendInterface::invalidate() - * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple() - * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateAll() - */ - public function invalidateTags(array $tags); - - /** * Marks all cache items as invalid. * * Invalid items may be returned in later calls to get(), if the $allow_invalid @@ -222,7 +186,7 @@ public function invalidateTags(array $tags); * @see \Drupal\Core\Cache\CacheBackendInterface::deleteAll() * @see \Drupal\Core\Cache\CacheBackendInterface::invalidate() * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateMultiple() - * @see \Drupal\Core\Cache\CacheBackendInterface::invalidateTags() + * @see \Drupal\Core\Cache\CacheTagBackendInterface::invalidateTags() */ public function invalidateAll(); diff --git a/core/lib/Drupal/Core/Cache/CacheTagBackendBase.php b/core/lib/Drupal/Core/Cache/CacheTagBackendBase.php new file mode 100644 index 0000000..4e2c190 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/CacheTagBackendBase.php @@ -0,0 +1,36 @@ + $values) { + if (is_array($values)) { + foreach ($values as $value) { + $flat_tags[] = "$namespace:$value"; + } + } + else { + $flat_tags[] = "$namespace:$values"; + } + } + return $flat_tags; + } +} diff --git a/core/lib/Drupal/Core/Cache/CacheTagBackendFactory.php b/core/lib/Drupal/Core/Cache/CacheTagBackendFactory.php new file mode 100644 index 0000000..a632449 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/CacheTagBackendFactory.php @@ -0,0 +1,57 @@ +settings = $settings; + } + + /** + * Instantiates a cache tag class. + * + * By default, this returns an instance of the + * Drupal\Core\Cache\DatabaseBackendTag class. + * + * @return \Drupal\Core\Cache\CacheTagBackendInterface + * The cache tag instance. + */ + public function get() { + $cache_tag_service = $this->settings->get('cache_tag_service'); + + if (isset($cache_tag_service)) { + $service_name = $cache_tag_service; + } + else { + $service_name = 'cache.tag.database'; + } + + return $this->container->get($service_name); + } + +} diff --git a/core/lib/Drupal/Core/Cache/CacheTagBackendInterface.php b/core/lib/Drupal/Core/Cache/CacheTagBackendInterface.php new file mode 100644 index 0000000..6e224a1 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/CacheTagBackendInterface.php @@ -0,0 +1,73 @@ +deleted should be set to TRUE if item was deleted via tags and + * $item->valid should be set to FALSE if item was invalidated. + */ + public function prepareGet(&$item); + + /** + * Clears cache tag service internal caches. + */ + public function clearCache(); +} diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php index cf2b264..1c49c26 100644 --- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php +++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php @@ -25,7 +25,6 @@ class DatabaseBackend implements CacheBackendInterface { */ protected $bin; - /** * The database connection. * @@ -34,21 +33,32 @@ class DatabaseBackend implements CacheBackendInterface { protected $connection; /** + * The cache tag service. + * + * @var \Drupal\Core\Cache\CacheTagBackendInterface + */ + protected $cacheTag; + + /** * Constructs a DatabaseBackend object. * * @param \Drupal\Core\Database\Connection $connection * The database connection. + * @param \Drupal\Core\Cache\CacheTagBackendInterface + * The cache tag service. * @param string $bin * The cache bin for which the object is created. */ - public function __construct(Connection $connection, $bin) { + public function __construct(Connection $connection, CacheTagBackendInterface $cache_tag, $bin) { // All cache tables should be prefixed with 'cache_', except for the // default 'cache' bin. if ($bin != 'cache') { $bin = 'cache_' . $bin; } $this->bin = $bin; + $this->connection = $connection; + $this->cacheTag = $cache_tag; } /** @@ -111,19 +121,17 @@ protected function prepareItem($cache, $allow_invalid) { $cache->tags = $cache->tags ? explode(' ', $cache->tags) : array(); - $checksum = $this->checksumTags($cache->tags); - - // Check if deleteTags() has been called with any of the entry's tags. - if ($cache->checksum_deletions != $checksum['deletions']) { - return FALSE; - } - // Check expire time. - $cache->valid = $cache->expire == Cache::PERMANENT || $cache->expire >= REQUEST_TIME; + $cache->valid = $time_valid = $cache->expire == Cache::PERMANENT || $cache->expire >= REQUEST_TIME; - // Check if invalidateTags() has been called with any of the entry's tags. - if ($cache->checksum_invalidations != $checksum['invalidations']) { - $cache->valid = FALSE; + $this->cacheTag->prepareGet($cache); + if ($cache->deleted) { + $this->delete($cache->cid); + return FALSE; + } + + if ($time_valid && !$cache->valid) { + $this->invalidate($cache->cid); } if (!$allow_invalid && !$cache->valid) { @@ -165,7 +173,7 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array * Actually set the cache. */ protected function doSet($cid, $data, $expire, $tags) { - $flat_tags = $this->flattenTags($tags); + $flat_tags = $this->cacheTag->flattenTags($tags); $deleted_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::deletedTags', array()); $invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array()); // Remove tags that were already deleted or invalidated during this request @@ -179,7 +187,8 @@ protected function doSet($cid, $data, $expire, $tags) { unset($invalidated_tags[$tag]); } } - $checksum = $this->checksumTags($flat_tags); + $checksum = $this->cacheTag->checksumTags($flat_tags, TRUE); + $fields = array( 'serialized' => 0, 'created' => REQUEST_TIME, @@ -234,32 +243,6 @@ 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()); - $deleted_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::deletedTags', array()); - foreach ($this->flattenTags($tags) as $tag) { - // Only delete tags once per request unless they are written again. - if (isset($deleted_tags[$tag])) { - continue; - } - $deleted_tags[$tag] = TRUE; - unset($tag_cache[$tag]); - try { - $this->connection->merge('cache_tags') - ->insertFields(array('deletions' => 1)) - ->expression('deletions', 'deletions + 1') - ->key('tag', $tag) - ->execute(); - } - catch (\Exception $e) { - $this->catchException($e, 'cache_tags'); - } - } - } - - /** * Implements Drupal\Core\Cache\CacheBackendInterface::deleteAll(). */ public function deleteAll() { @@ -303,32 +286,6 @@ 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()); - $invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array()); - foreach ($this->flattenTags($tags) as $tag) { - // Only invalidate tags once per request unless they are written again. - if (isset($invalidated_tags[$tag])) { - continue; - } - $invalidated_tags[$tag] = TRUE; - unset($tag_cache[$tag]); - $this->connection->merge('cache_tags') - ->insertFields(array('invalidations' => 1)) - ->expression('invalidations', 'invalidations + 1') - ->key('tag', $tag) - ->execute(); - } - } - catch (\Exception $e) { - $this->catchException($e, 'cache_tags'); - } - } - - /** * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateAll(). */ public function invalidateAll() { @@ -360,70 +317,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 1d70164..7046026 100644 --- a/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php +++ b/core/lib/Drupal/Core/Cache/DatabaseBackendFactory.php @@ -19,12 +19,20 @@ class DatabaseBackendFactory implements CacheFactoryInterface { protected $connection; /** + * The cache tag factory service. + * + * @var \Drupal\Core\Cache\CacheTagBackendFactory + */ + protected $cacheTagFactory; + + /** * Constructs the DatabaseBackendFactory object. * * @param \Drupal\Core\Database\Connection $connection */ - function __construct(Connection $connection) { + function __construct(Connection $connection, CacheTagBackendFactory $cache_tag_factory) { $this->connection = $connection; + $this->cacheTagFactory = $cache_tag_factory; } /** @@ -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->cacheTagFactory->get(), $bin); } } diff --git a/core/lib/Drupal/Core/Cache/DatabaseTagBackend.php b/core/lib/Drupal/Core/Cache/DatabaseTagBackend.php new file mode 100644 index 0000000..6f4a143 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/DatabaseTagBackend.php @@ -0,0 +1,164 @@ +connection = $connection; + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) { + $invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array()); + $tag_cache = &drupal_static('Drupal\Core\Cache\CacheTagBackendInterface::tagCache', array()); + try { + foreach ($this->flattenTags($tags) as $tag) { + // Only invalidate tags once per request unless they are written again. + if (isset($invalidated_tags[$tag])) { + continue; + } + $invalidated_tags[$tag] = TRUE; + 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'); + } + } + + /** + * {@inheritdoc} + */ + public function deleteTags(array $tags) { + $deleted_tags = &drupal_static('Drupal\Core\Cache\DatabaseTagBackend::deletedTags', array()); + $tag_cache = &drupal_static('Drupal\Core\Cache\CacheTagBackendInterface::tagCache', array()); + foreach ($this->flattenTags($tags) as $tag) { + // Only delete tags once per request unless they are written again. + if (isset($deleted_tags[$tag])) { + continue; + } + $deleted_tags[$tag] = TRUE; + 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'); + } + } + } + + /** + * {@inheritdoc} + */ + public function checksumTags(array $tags, $set_context) { + $tag_cache = &drupal_static('Drupal\Core\Cache\CacheTagBackendInterface::tagCache', array()); + + if ($set_context) { + $deleted_tags = &drupal_static('Drupal\Core\Cache\DatabaseTagBackend::deletedTags', array()); + // Remove tags that were already deleted during this request from the static + // cache so that another deletion for them will be correctly updated. + foreach ($tags as $tag) { + if (isset($deleted_tags[$tag])) { + unset($deleted_tags[$tag]); + } + } + } + + $checksum = array( + 'invalidations' => 0, + 'deletions' => 0, + ); + + $query_tags = array_diff($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 ($tags as $tag) { + $checksum['invalidations'] += $tag_cache[$tag]['invalidations']; + $checksum['deletions'] += $tag_cache[$tag]['deletions']; + } + + return $checksum; + } + + /** + * {@inheritdoc} + */ + public function prepareGet(&$item) { + $checksum = $this->checksumTags($item->tags, FALSE); + + // Check if deleteTags() has been called with any of the entry's tags. + $item->deleted = $item->checksum_deletions != $checksum['deletions']; + + // Check if invalidateTags() has been called with any of the entry's tags. + if ($item->checksum_invalidations != $checksum['invalidations']) { + $item->valid = FALSE; + } + } + + /** + * 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; + } + } + + /** + * {@inheritdoc}. + */ + public function clearCache() { + // Nothing to do here. + } +} diff --git a/core/lib/Drupal/Core/Cache/ListCacheBinsPass.php b/core/lib/Drupal/Core/Cache/ListCacheBinsPass.php index 5381186..2031aed 100644 --- a/core/lib/Drupal/Core/Cache/ListCacheBinsPass.php +++ b/core/lib/Drupal/Core/Cache/ListCacheBinsPass.php @@ -11,20 +11,20 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; /** - * Adds cache_bins parameter to the container. + * Adds cache_bins and cache_tags parameters to the container. */ class ListCacheBinsPass implements CompilerPassInterface { /** - * Implements CompilerPassInterface::process(). - * - * Collects the cache bins into the cache_bins parameter. + * {@inheritdoc} */ public function process(ContainerBuilder $container) { - $cache_bins = array(); - foreach ($container->findTaggedServiceIds('cache.bin') as $id => $attributes) { - $cache_bins[$id] = substr($id, strpos($id, '.') + 1); + foreach (array('cache.bin' => 'cache_bins', 'cache.tag' => 'cache_tags') as $name => $param) { + $params = array(); + foreach ($container->findTaggedServiceIds($name) as $id => $attributes) { + $params[$id] = substr($id, strpos($id, '.') + 1); + } + $container->setParameter($param, $params); } - $container->setParameter('cache_bins', $cache_bins); } } diff --git a/core/lib/Drupal/Core/Cache/MemoryBackend.php b/core/lib/Drupal/Core/Cache/MemoryBackend.php index c4e16f7..7d104f1 100644 --- a/core/lib/Drupal/Core/Cache/MemoryBackend.php +++ b/core/lib/Drupal/Core/Cache/MemoryBackend.php @@ -25,12 +25,23 @@ class MemoryBackend implements CacheBackendInterface { protected $cache = array(); /** + * The cache tag service. + * + * @var \Drupal\Core\Cache\CacheTagBackendInterface + */ + protected $cacheTag; + + /** * Constructs a MemoryBackend object. * + * + * @param \Drupal\Core\Cache\CacheTagBackendInterface + * The cache tag service. * @param string $bin * The cache bin for which the object is created. */ - public function __construct($bin) { + public function __construct(CacheTagBackendInterface $cache_tag, $bin) { + $this->cacheTag = $cache_tag; } /** @@ -84,7 +95,17 @@ protected function prepareItem($cache, $allow_invalid) { } // Check expire time. - $cache->valid = $cache->expire == Cache::PERMANENT || $cache->expire >= REQUEST_TIME; + $cache->valid = $time_valid = $cache->expire == Cache::PERMANENT || $cache->expire >= REQUEST_TIME; + + $this->cacheTag->prepareGet($cache); + if ($cache->deleted) { + $this->delete($cache->cid); + return FALSE; + } + + if ($time_valid && !$cache->valid) { + $this->invalidate($cache->cid); + } if (!$allow_invalid && !$cache->valid) { return FALSE; @@ -97,12 +118,16 @@ protected function prepareItem($cache, $allow_invalid) { * Implements Drupal\Core\Cache\CacheBackendInterface::set(). */ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) { + $flat_tags = $this->cacheTag->flattenTags($tags); + $checksum = $this->cacheTag->checksumTags($flat_tags, TRUE); $this->cache[$cid] = (object) array( 'cid' => $cid, 'data' => $data, 'created' => REQUEST_TIME, 'expire' => $expire, - 'tags' => $this->flattenTags($tags), + 'tags' => $flat_tags, + 'checksum_invalidations' => $checksum['invalidations'], + 'checksum_deletions' => $checksum['deletions'], ); } @@ -121,18 +146,6 @@ public function deleteMultiple(array $cids) { } /** - * Implements Drupal\Core\Cache\CacheBackendInterface::deleteTags(). - */ - public function deleteTags(array $tags) { - $flat_tags = $this->flattenTags($tags); - foreach ($this->cache as $cid => $item) { - if (array_intersect($flat_tags, $item->tags)) { - unset($this->cache[$cid]); - } - } - } - - /** * Implements Drupal\Core\Cache\CacheBackendInterface::deleteAll(). */ public function deleteAll() { @@ -158,18 +171,6 @@ public function invalidateMultiple(array $cids) { } /** - * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateTags(). - */ - public function invalidateTags(array $tags) { - $flat_tags = $this->flattenTags($tags); - foreach ($this->cache as $cid => $item) { - if (array_intersect($flat_tags, $item->tags)) { - $this->cache[$cid]->expire = REQUEST_TIME - 1; - } - } - } - - /** * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateAll(). */ public function invalidateAll() { diff --git a/core/lib/Drupal/Core/Cache/MemoryBackendFactory.php b/core/lib/Drupal/Core/Cache/MemoryBackendFactory.php index c9e288d..acaa82f 100644 --- a/core/lib/Drupal/Core/Cache/MemoryBackendFactory.php +++ b/core/lib/Drupal/Core/Cache/MemoryBackendFactory.php @@ -10,10 +10,26 @@ class MemoryBackendFactory implements CacheFactoryInterface { /** + * The cache tag factory service. + * + * @var \Drupal\Core\Cache\CacheTagBackendFactory + */ + protected $cacheTagFactory; + + /** + * Constructs the DatabaseBackendFactory object. + * + * @param \Drupal\Core\Database\Connection $connection + */ + function __construct(CacheTagBackendFactory $cache_tag_factory) { + $this->cacheTagFactory = $cache_tag_factory; + } + + /** * {@inheritdoc} */ function get($bin) { - return new MemoryBackend($bin); + return new MemoryBackend($this->cacheTagFactory->get(), $bin); } } diff --git a/core/lib/Drupal/Core/Cache/MemoryTagBackend.php b/core/lib/Drupal/Core/Cache/MemoryTagBackend.php new file mode 100644 index 0000000..4920b05 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/MemoryTagBackend.php @@ -0,0 +1,95 @@ +storage[$tag])) { + $this->storage[$tag] = array( + 'invalidations' => 0, + 'deletions' => 0, + ); + } + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) { + foreach ($this->flattenTags($tags) as $tag) { + $this->ensureItem($tag); + $this->storage[$tag]['invalidations']++; + } + } + + /** + * {@inheritdoc} + */ + public function deleteTags(array $tags) { + foreach ($this->flattenTags($tags) as $tag) { + $this->ensureItem($tag); + $this->storage[$tag]['deletions']++; + } + } + + /** + * {@inheritdoc} + */ + public function checksumTags(array $tags, $set_context) { + $checksum = array( + 'invalidations' => 0, + 'deletions' => 0, + ); + + foreach ($tags as $tag) { + $this->ensureItem($tag); + $checksum['invalidations'] += $this->storage[$tag]['invalidations']; + $checksum['deletions'] += $this->storage[$tag]['deletions']; + } + + return $checksum; + } + + /** + * {@inheritdoc} + */ + public function prepareGet(&$item) { + $checksum = $this->checksumTags($item->tags, FALSE); + + // Check if deleteTags() has been called with any of the entry's tags. + $item->deleted = $item->checksum_deletions != $checksum['deletions']; + + // Check if invalidateTags() has been called with any of the entry's tags. + if ($item->checksum_invalidations != $checksum['invalidations']) { + $item->valid = FALSE; + } + } + + /** + * {@inheritdoc}. + */ + public function clearCache() { + // Nothing to be done here. + } +} diff --git a/core/lib/Drupal/Core/Cache/NullBackend.php b/core/lib/Drupal/Core/Cache/NullBackend.php index 94a426d..7a4e593 100644 --- a/core/lib/Drupal/Core/Cache/NullBackend.php +++ b/core/lib/Drupal/Core/Cache/NullBackend.php @@ -65,11 +65,6 @@ public function deleteMultiple(array $cids) {} public function deleteAll() {} /** - * Implements Drupal\Core\Cache\CacheBackendInterface::deleteTags(). - */ - public function deleteTags(array $tags) {} - - /** * Implements Drupal\Core\Cache\CacheBackendInterface::invalidate(). */ public function invalidate($cid) {} @@ -80,11 +75,6 @@ public function invalidate($cid) {} public function invalidateMultiple(array $cids) {} /** - * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateTags(). - */ - public function invalidateTags(array $tags) {} - - /** * Implements Drupal\Core\Cache\CacheBackendInterface::invalidateAll(). */ public function invalidateAll() {} diff --git a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php index 4776214..bcf8c06 100644 --- a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php +++ b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php @@ -36,7 +36,8 @@ public function register(ContainerBuilder $container) { $container->register('module_handler', 'Drupal\Core\Extension\UpdateModuleHandler') ->addArgument('%container.modules%'); $container - ->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory'); + ->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory') + ->addArgument(new Reference('cache_tag_factory')); $container ->register('router.builder', 'Drupal\Core\Routing\RouteBuilderStatic'); diff --git a/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php b/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php index 2389a62..f447174 100644 --- a/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php +++ b/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php @@ -10,6 +10,7 @@ use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ServiceProviderInterface; use Drupal\Core\DependencyInjection\ServiceModifierInterface; +use Symfony\Component\DependencyInjection\Reference; /** * Service provider for the early installer environment. @@ -30,9 +31,11 @@ public function register(ContainerBuilder $container) { $container->register('config.storage', 'Drupal\Core\Config\InstallStorage'); // Replace services with in-memory implementations. + $container->register('cache.tag.memory', 'Drupal\Core\Cache\MemoryTagBackend'); foreach (array('bootstrap', 'config', 'cache', 'menu', 'page', 'path') as $bin) { $container ->register("cache.$bin", 'Drupal\Core\Cache\MemoryBackend') + ->addArgument(new Reference('cache.tag.memory')) ->addArgument($bin); } $container diff --git a/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php b/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php index 99e8de7..4654baf 100644 --- a/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php +++ b/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php @@ -8,6 +8,7 @@ namespace Drupal\hal\Tests; use Drupal\Core\Cache\MemoryBackend; +use Drupal\Core\Cache\MemoryTagBackend; use Drupal\Core\Language\Language; use Drupal\hal\Encoder\JsonEncoder; use Drupal\hal\Normalizer\EntityNormalizer; @@ -120,7 +121,7 @@ function setUp() { 'bundle' => 'entity_test', ))->save(); - $link_manager = new LinkManager(new TypeLinkManager(new MemoryBackend('cache')), new RelationLinkManager(new MemoryBackend('cache'))); + $link_manager = new LinkManager(new TypeLinkManager(new MemoryBackend(new MemoryTagBackend(), 'cache')), new RelationLinkManager(new MemoryBackend(new MemoryTagBackend(), 'cache'))); // Set up the mock serializer. $normalizers = array( diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleTranslationUiTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleTranslationUiTest.php index fa33010..56e7605 100644 --- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleTranslationUiTest.php +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleTranslationUiTest.php @@ -143,7 +143,7 @@ function testStringTranslation() { // Reset the tag cache on the tester side in order to pick up the call to // Cache::deleteTags() on the tested side. - drupal_static_reset('Drupal\Core\Cache\CacheBackendInterface::tagCache'); + $this->container->get('cache.tag.database')->clearCache(); $this->assertTrue($name != $translation && t($name, array(), array('langcode' => $langcode)) == $translation, 't() works for non-English.'); // Refresh the locale() cache to get fresh data from t() below. We are in diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php index e3c916d..b51e719 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php @@ -225,7 +225,10 @@ public function containerBuild(ContainerBuilder $container) { $this->container->setParameter('language.default_values', Language::$defaultValues); $container->register('lock', 'Drupal\Core\Lock\NullLockBackend'); - $container->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory'); + $this->settingsSet('cache', array('default' => 'cache.backend.memory')); + $this->settingsSet('cache_tag_service', 'cache.tag.memory'); + $container->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory') + ->addArgument(new Reference('cache_tag_factory'));; $container ->register('config.storage', 'Drupal\Core\Config\FileStorage') diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 3e8f51b..6d1a18d 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -802,6 +802,10 @@ protected function setUp() { // Execute the non-interactive installer. require_once DRUPAL_ROOT . '/core/includes/install.core.inc'; + $this->settingsSet('cache', array('default' => 'cache.backend.memory')); + $this->settingsSet('cache_tag_service', 'cache.tag.memory'); + $parameters = $this->installParameters(); + install_drupal($parameters); // Import new settings.php written by the installer. @@ -1049,8 +1053,9 @@ protected function resetAll() { */ protected function refreshVariables() { // Clear the tag cache. - drupal_static_reset('Drupal\Core\Cache\CacheBackendInterface::tagCache'); - drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::deletedTags'); + $this->container->get('cache.tag.database')->clearCache(); + drupal_static_reset('Drupal\Core\Cache\CacheTagBackendInterface::tagCache'); + drupal_static_reset('Drupal\Core\Cache\DatabaseTagBackend::deletedTags'); drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::invalidatedTags'); $this->container->get('config.factory')->reset(); diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/BackendChainUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Cache/BackendChainUnitTest.php index 3323b1a..dc97dc4 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Cache/BackendChainUnitTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Cache/BackendChainUnitTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Cache\BackendChain; use Drupal\Core\Cache\MemoryBackend; +use Drupal\Core\Cache\MemoryTagBackend; /** * Tests BackendChain using GenericCacheBackendUnitTestBase. @@ -27,10 +28,11 @@ protected function createCacheBackend($bin) { $chain = new BackendChain($bin); // We need to create some various backends in the chain. + $cache_tag = new MemoryTagBackend(); $chain - ->appendBackend(new MemoryBackend('foo')) - ->prependBackend(new MemoryBackend('bar')) - ->appendBackend(new MemoryBackend('baz')); + ->appendBackend(new MemoryBackend($cache_tag, 'foo')) + ->prependBackend(new MemoryBackend($cache_tag, 'bar')) + ->appendBackend(new MemoryBackend($cache_tag, 'baz')); return $chain; } 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..d344d58 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_factory')->get(), $bin); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php index e558816..a5142db 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php +++ b/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php @@ -353,66 +353,6 @@ public function testDeleteMultiple() { } /** - * Tests Drupal\Core\Cache\CacheBackendInterface::deleteTags(). - */ - function testDeleteTags() { - $backend = $this->getCacheBackend(); - - // Create two cache entries with the same tag and tag value. - $backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, array('test_tag' => 2)); - $backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('test_tag' => 2)); - $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Two cache items were created.'); - - // Delete test_tag of value 1. This should delete both entries. - $backend->deleteTags(array('test_tag' => 2)); - $this->assertFalse($backend->get('test_cid_invalidate1') || $backend->get('test_cid_invalidate2'), 'Two cache items invalidated after deleting a cache tag.'); - $this->assertFalse($backend->get('test_cid_invalidate1', TRUE) || $backend->get('test_cid_invalidate2', TRUE), 'Two cache items deleted after deleting a cache tag.'); - - // Create two cache entries with the same tag and an array tag value. - $backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, array('test_tag' => array(1))); - $backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('test_tag' => array(1))); - $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Two cache items were created.'); - - // Delete test_tag of value 1. This should delete both entries. - $backend->deleteTags(array('test_tag' => array(1))); - $this->assertFalse($backend->get('test_cid_invalidate1') || $backend->get('test_cid_invalidate2'), 'Two cache items invalidated after deleted a cache tag.'); - $this->assertFalse($backend->get('test_cid_invalidate1', TRUE) || $backend->get('test_cid_invalidate2', TRUE), 'Two cache items deleted after deleting a cache tag.'); - - // Create three cache entries with a mix of tags and tag values. - $backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, array('test_tag' => array(1))); - $backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('test_tag' => array(2))); - $backend->set('test_cid_invalidate3', $this->defaultValue, Cache::PERMANENT, array('test_tag_foo' => array(3))); - $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2') && $backend->get('test_cid_invalidate3'), 'Three cached items were created.'); - $backend->deleteTags(array('test_tag_foo' => array(3))); - $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Cached items not matching the tag were not deleted.'); - $this->assertFalse($backend->get('test_cid_invalidated3', TRUE), 'Cache item matching the tag was deleted.'); - - // Create cache entry in multiple bins. Two cache entries - // (test_cid_invalidate1 and test_cid_invalidate2) still exist from previous - // tests. - $tags = array('test_tag' => array(1, 2, 3)); - $bins = array('path', 'bootstrap', 'page'); - foreach ($bins as $bin) { - $this->getCacheBackend($bin)->set('test', $this->defaultValue, Cache::PERMANENT, $tags); - $this->assertTrue($this->getCacheBackend($bin)->get('test'), 'Cache item was set in bin.'); - } - - // Delete tag in mulitple bins. - foreach ($bins as $bin) { - $this->getCacheBackend($bin)->deleteTags(array('test_tag' => array(2))); - } - - // Test that cache entry has been deleted in multple bins. - foreach ($bins as $bin) { - $this->assertFalse($this->getCacheBackend($bin)->get('test', TRUE), 'Tag deletion affected item in bin.'); - } - // Test that the cache entry with a matching tag has been invalidated. - $this->assertFalse($this->getCacheBackend($bin)->get('test_cid_invalidate2', TRUE), 'Cache items matching tag were deleted.'); - // Test that the cache entry with without a matching tag still exists. - $this->assertTrue($this->getCacheBackend($bin)->get('test_cid_invalidate1'), 'Cache items not matching tag were not invalidated.'); - } - - /** * Test Drupal\Core\Cache\CacheBackendInterface::deleteAll(). */ public function testDeleteAll() { @@ -460,66 +400,6 @@ function testInvalidate() { } /** - * Tests Drupal\Core\Cache\CacheBackendInterface::invalidateTags(). - */ - function testInvalidateTags() { - $backend = $this->getCacheBackend(); - - // Create two cache entries with the same tag and tag value. - $backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, array('test_tag' => 2)); - $backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('test_tag' => 2)); - $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Two cache items were created.'); - - // Invalidate test_tag of value 1. This should invalidate both entries. - $backend->invalidateTags(array('test_tag' => 2)); - $this->assertFalse($backend->get('test_cid_invalidate1') || $backend->get('test_cid_invalidate2'), 'Two cache items invalidated after invalidating a cache tag.'); - $this->assertTrue($backend->get('test_cid_invalidate1', TRUE) && $backend->get('test_cid_invalidate2', TRUE), 'Cache items not deleted after invalidating a cache tag.'); - - // Create two cache entries with the same tag and an array tag value. - $backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, array('test_tag' => array(1))); - $backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('test_tag' => array(1))); - $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Two cache items were created.'); - - // Invalidate test_tag of value 1. This should invalidate both entries. - $backend->invalidateTags(array('test_tag' => array(1))); - $this->assertFalse($backend->get('test_cid_invalidate1') || $backend->get('test_cid_invalidate2'), 'Two caches removed after invalidating a cache tag.'); - $this->assertTrue($backend->get('test_cid_invalidate1', TRUE) && $backend->get('test_cid_invalidate2', TRUE), 'Cache items not deleted after invalidating a cache tag.'); - - // Create three cache entries with a mix of tags and tag values. - $backend->set('test_cid_invalidate1', $this->defaultValue, Cache::PERMANENT, array('test_tag' => array(1))); - $backend->set('test_cid_invalidate2', $this->defaultValue, Cache::PERMANENT, array('test_tag' => array(2))); - $backend->set('test_cid_invalidate3', $this->defaultValue, Cache::PERMANENT, array('test_tag_foo' => array(3))); - $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2') && $backend->get('test_cid_invalidate3'), 'Three cached items were created.'); - $backend->invalidateTags(array('test_tag_foo' => array(3))); - $this->assertTrue($backend->get('test_cid_invalidate1') && $backend->get('test_cid_invalidate2'), 'Cache items not matching the tag were not invalidated.'); - $this->assertFalse($backend->get('test_cid_invalidated3'), 'Cached item matching the tag was removed.'); - - // Create cache entry in multiple bins. Two cache entries - // (test_cid_invalidate1 and test_cid_invalidate2) still exist from previous - // tests. - $tags = array('test_tag' => array(1, 2, 3)); - $bins = array('path', 'bootstrap', 'page'); - foreach ($bins as $bin) { - $this->getCacheBackend($bin)->set('test', $this->defaultValue, Cache::PERMANENT, $tags); - $this->assertTrue($this->getCacheBackend($bin)->get('test'), 'Cache item was set in bin.'); - } - - // Invalidate tag in mulitple bins. - foreach ($bins as $bin) { - $this->getCacheBackend($bin)->invalidateTags(array('test_tag' => array(2))); - } - - // Test that cache entry has been invalidated in multple bins. - foreach ($bins as $bin) { - $this->assertFalse($this->getCacheBackend($bin)->get('test'), 'Tag invalidation affected item in bin.'); - } - // Test that the cache entry with a matching tag has been invalidated. - $this->assertFalse($this->getCacheBackend($bin)->get('test_cid_invalidate2'), 'Cache items matching tag were invalidated.'); - // Test that the cache entry with without a matching tag still exists. - $this->assertTrue($this->getCacheBackend($bin)->get('test_cid_invalidate1'), 'Cache items not matching tag were not invalidated.'); - } - - /** * Test Drupal\Core\Cache\CacheBackendInterface::invalidateAll(). */ public function testInvalidateAll() { diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/MemoryBackendUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Cache/MemoryBackendUnitTest.php index d3a29e3..f6cf75c 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Cache/MemoryBackendUnitTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Cache/MemoryBackendUnitTest.php @@ -8,6 +8,7 @@ namespace Drupal\system\Tests\Cache; use Drupal\Core\Cache\MemoryBackend; +use Drupal\Core\Cache\MemoryTagBackend; /** * Tests MemoryBackend using GenericCacheBackendUnitTestBase. @@ -29,6 +30,6 @@ public static function getInfo() { * A new MemoryBackend object. */ protected function createCacheBackend($bin) { - return new MemoryBackend($bin); + return new MemoryBackend(new MemoryTagBackend(), $bin); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Path/AliasTest.php b/core/modules/system/lib/Drupal/system/Tests/Path/AliasTest.php index 5f5d368..52ff543 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Path/AliasTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Path/AliasTest.php @@ -8,6 +8,7 @@ namespace Drupal\system\Tests\Path; use Drupal\Core\Cache\MemoryCounterBackend; +use Drupal\Core\Cache\MemoryTagBackend; use Drupal\Core\Path\Path; use Drupal\Core\Database\Database; use Drupal\Core\Path\AliasManager; @@ -165,7 +166,7 @@ function testWhitelist() { $connection = Database::getConnection(); $this->fixtures->createTables($connection); - $memoryCounterBackend = new MemoryCounterBackend('cache'); + $memoryCounterBackend = new MemoryCounterBackend(new MemoryTagBackend(), 'cache'); // Create AliasManager and Path object. $whitelist = new AliasWhitelist('path_alias_whitelist', $memoryCounterBackend, $this->container->get('lock'), $this->container->get('state'), $connection); diff --git a/core/modules/system/lib/Drupal/system/Tests/Plugin/CacheDecoratorTest.php b/core/modules/system/lib/Drupal/system/Tests/Plugin/CacheDecoratorTest.php index 532fea8..75fe0a6 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Plugin/CacheDecoratorTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Plugin/CacheDecoratorTest.php @@ -7,8 +7,8 @@ namespace Drupal\system\Tests\Plugin; -use Drupal\Core\Cache\MemoryBackendFactory; use Drupal\Core\Cache\MemoryBackend; +use Drupal\Core\Cache\MemoryTagBackend; use Drupal\system\Tests\Plugin\Discovery\DiscoveryTestBase; use Drupal\Component\Plugin\Discovery\StaticDiscovery; use Drupal\Core\Plugin\Discovery\CacheDecorator; @@ -47,7 +47,7 @@ public function setUp() { // Use a non-db cache backend, so that we can use DiscoveryTestBase (which // extends UnitTestBase). // @todo switch to injecting the MemoryBackend http://drupal.org/node/1903346 - \Drupal::getContainer()->set("cache.$this->cacheBin", new MemoryBackend($this->cacheBin)); + \Drupal::getContainer()->set("cache.$this->cacheBin", new MemoryBackend(new MemoryTagBackend(), $this->cacheBin)); // Create discovery objects to test. $this->emptyDiscovery = new StaticDiscovery(); diff --git a/core/tests/Drupal/Tests/Core/Cache/BackendChainImplementationUnitTest.php b/core/tests/Drupal/Tests/Core/Cache/BackendChainImplementationUnitTest.php index 75c0a97..7b36ce9 100644 --- a/core/tests/Drupal/Tests/Core/Cache/BackendChainImplementationUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Cache/BackendChainImplementationUnitTest.php @@ -10,6 +10,7 @@ use Drupal\Core\Cache\BackendChain; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\MemoryBackend; +use Drupal\Core\Cache\MemoryTagBackend; use Drupal\Tests\UnitTestCase; /** @@ -59,9 +60,10 @@ public function setUp() { parent::setUp(); // Set up three memory backends to be used in the chain. - $this->firstBackend = new MemoryBackend('foo'); - $this->secondBackend = new MemoryBackend('bar'); - $this->thirdBackend = new MemoryBackend('baz'); + $cache_tag = new MemoryTagBackend(); + $this->firstBackend = new MemoryBackend($cache_tag, 'foo'); + $this->secondBackend = new MemoryBackend($cache_tag, 'bar'); + $this->thirdBackend = new MemoryBackend($cache_tag, 'baz'); // Set an initial fixed dataset for all testing. The next three data // collections will test two edge cases (last backend has the data, and @@ -232,83 +234,6 @@ public function testDeleteAllPropagation() { } /** - * Test that the delete tags operation is propagated to all backends - * in the chain. - */ - public function testDeleteTagsPropagation() { - // Create two cache entries with the same tag and tag value. - $this->chain->set('test_cid_clear1', 'foo', Cache::PERMANENT, array('test_tag' => 2)); - $this->chain->set('test_cid_clear2', 'foo', Cache::PERMANENT, array('test_tag' => 2)); - $this->assertNotSame(FALSE, $this->firstBackend->get('test_cid_clear1') - && $this->firstBackend->get('test_cid_clear2') - && $this->secondBackend->get('test_cid_clear1') - && $this->secondBackend->get('test_cid_clear2') - && $this->thirdBackend->get('test_cid_clear1') - && $this->thirdBackend->get('test_cid_clear2'), - 'Two cache items were created in all backends.'); - - // Invalidate test_tag of value 1. This should invalidate both entries. - $this->chain->deleteTags(array('test_tag' => 2)); - $this->assertSame(FALSE, $this->firstBackend->get('test_cid_clear1') - && $this->firstBackend->get('test_cid_clear2') - && $this->secondBackend->get('test_cid_clear1') - && $this->secondBackend->get('test_cid_clear2') - && $this->thirdBackend->get('test_cid_clear1') - && $this->thirdBackend->get('test_cid_clear2'), - 'Two caches removed from all backends after clearing a cache tag.'); - - // Create two cache entries with the same tag and an array tag value. - $this->chain->set('test_cid_clear1', 'foo', Cache::PERMANENT, array('test_tag' => array(1))); - $this->chain->set('test_cid_clear2', 'foo', Cache::PERMANENT, array('test_tag' => array(1))); - $this->assertNotSame(FALSE, $this->firstBackend->get('test_cid_clear1') - && $this->firstBackend->get('test_cid_clear2') - && $this->secondBackend->get('test_cid_clear1') - && $this->secondBackend->get('test_cid_clear2') - && $this->thirdBackend->get('test_cid_clear1') - && $this->thirdBackend->get('test_cid_clear2'), - 'Two cache items were created in all backends.'); - - // Invalidate test_tag of value 1. This should invalidate both entries. - $this->chain->deleteTags(array('test_tag' => array(1))); - $this->assertSame(FALSE, $this->firstBackend->get('test_cid_clear1') - && $this->firstBackend->get('test_cid_clear2') - && $this->secondBackend->get('test_cid_clear1') - && $this->secondBackend->get('test_cid_clear2') - && $this->thirdBackend->get('test_cid_clear1') - && $this->thirdBackend->get('test_cid_clear2'), - 'Two caches removed from all backends after clearing a cache tag.'); - - // Create three cache entries with a mix of tags and tag values. - $this->chain->set('test_cid_clear1', 'foo', Cache::PERMANENT, array('test_tag' => array(1))); - $this->chain->set('test_cid_clear2', 'foo', Cache::PERMANENT, array('test_tag' => array(2))); - $this->chain->set('test_cid_clear3', 'foo', Cache::PERMANENT, array('test_tag_foo' => array(3))); - $this->assertNotSame(FALSE, $this->firstBackend->get('test_cid_clear1') - && $this->firstBackend->get('test_cid_clear2') - && $this->firstBackend->get('test_cid_clear3') - && $this->secondBackend->get('test_cid_clear1') - && $this->secondBackend->get('test_cid_clear2') - && $this->secondBackend->get('test_cid_clear3') - && $this->thirdBackend->get('test_cid_clear1') - && $this->thirdBackend->get('test_cid_clear2') - && $this->thirdBackend->get('test_cid_clear3'), - 'Three cached items were created in all backends.'); - - $this->chain->deleteTags(array('test_tag_foo' => array(3))); - $this->assertNotSame(FALSE, $this->firstBackend->get('test_cid_clear1') - && $this->firstBackend->get('test_cid_clear2') - && $this->secondBackend->get('test_cid_clear1') - && $this->secondBackend->get('test_cid_clear2') - && $this->thirdBackend->get('test_cid_clear1') - && $this->thirdBackend->get('test_cid_clear2'), - 'Cached items not matching the tag were not cleared from any of the backends.'); - - $this->assertSame(FALSE, $this->firstBackend->get('test_cid_clear3') - && $this->secondBackend->get('test_cid_clear3') - && $this->thirdBackend->get('test_cid_clear3'), - 'Cached item matching the tag was removed from all backends.'); - } - - /** * Test that removing bin propagates to all backends. */ public function testRemoveBin() { diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheCollectorTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheCollectorTest.php index 05a987c..0b85e6d 100644 --- a/core/tests/Drupal/Tests/Core/Cache/CacheCollectorTest.php +++ b/core/tests/Drupal/Tests/Core/Cache/CacheCollectorTest.php @@ -27,6 +27,13 @@ class CacheCollectorTest extends UnitTestCase { protected $cache; /** + * The cache tag backend to use. + * + * @var \Drupal\Core\Cache\CacheTagBackendInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $cacheTag; + + /** * The lock backend that should be used. * * @var \PHPUnit_Framework_MockObject_MockObject @@ -60,11 +67,12 @@ public static function getInfo() { */ protected function setUp() { $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + $this->cacheTag = $this->getMock('Drupal\Core\Cache\CacheTagBackendInterface'); $this->lock = $this->getMock('Drupal\Core\Lock\LockBackendInterface'); $this->cid = $this->randomName(); $this->collector = new CacheCollectorHelper($this->cid, $this->cache, $this->lock); - $this->getContainerWithCacheBins($this->cache); + $this->getContainerWithCacheTags($this->cacheTag); } @@ -387,7 +395,7 @@ public function testUpdateCacheClear() { $this->cache->expects($this->once()) ->method('delete') ->with($this->cid); - $this->cache->expects($this->never()) + $this->cacheTag->expects($this->never()) ->method('deleteTags'); $this->collector->clear(); $this->assertEquals($value, $this->collector->get($key)); @@ -414,7 +422,7 @@ public function testUpdateCacheClearTags() { // Clear the collected cache using the tags, should call it again. $this->cache->expects($this->never()) ->method('delete'); - $this->cache->expects($this->once()) + $this->cacheTag->expects($this->once()) ->method('deleteTags') ->with($tags); $this->collector->clear(); diff --git a/core/tests/Drupal/Tests/Core/Config/CachedStorageTest.php b/core/tests/Drupal/Tests/Core/Config/CachedStorageTest.php index 6c60af4..a742bf7 100644 --- a/core/tests/Drupal/Tests/Core/Config/CachedStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Config/CachedStorageTest.php @@ -2,11 +2,11 @@ namespace Drupal\Tests\Core\Config; +use Drupal\Core\Cache\MemoryTagBackend; use Drupal\Tests\UnitTestCase; use Drupal\Core\Config\CachedStorage; use Drupal\Core\Cache\MemoryBackend; use Drupal\Core\Cache\NullBackend; -use Drupal\Core\Cache\CacheBackendInterface; /** * Tests the interaction of cache and file storage in CachedStorage. @@ -51,7 +51,7 @@ public function testListAllPrimedPersistentCache() { $storage->expects($this->never())->method('listAll'); $response = array("$prefix." . $this->randomName(), "$prefix." . $this->randomName()); - $cache = new MemoryBackend(__FUNCTION__); + $cache = new MemoryBackend(new MemoryTagBackend(), __FUNCTION__); $cache->set('find:' . $prefix, $response); $cachedStorage = new CachedStorage($storage, $cache); $this->assertEquals($response, $cachedStorage->listAll($prefix)); @@ -75,7 +75,7 @@ public function testGetMultipleOnPrimedCache() { ); $storage = $this->getMock('Drupal\Core\Config\StorageInterface'); $storage->expects($this->never())->method('readMultiple'); - $cache = new MemoryBackend(__FUNCTION__); + $cache = new MemoryBackend(new MemoryTagBackend(), __FUNCTION__); foreach ($configCacheValues as $key => $value) { $cache->set($key, $value); } @@ -102,7 +102,7 @@ public function testGetMultipleOnPartiallyPrimedCache() { 'foo' => 'bar', ), ); - $cache = new MemoryBackend(__FUNCTION__); + $cache = new MemoryBackend(new MemoryTagBackend(), __FUNCTION__); foreach ($configCacheValues as $key => $value) { $cache->set($key, $value); } @@ -137,7 +137,7 @@ public function testGetMultipleOnPartiallyPrimedCache() { */ public function testReadNonExistentFileCacheMiss() { $name = 'config.does_not_exist'; - $cache = new MemoryBackend(__FUNCTION__); + $cache = new MemoryBackend(new MemoryTagBackend(), __FUNCTION__); $storage = $this->getMock('Drupal\Core\Config\StorageInterface'); $storage->expects($this->once()) ->method('read') @@ -157,7 +157,7 @@ public function testReadNonExistentFileCacheMiss() { */ public function testReadNonExistentFileCached() { $name = 'config.does_not_exist'; - $cache = new MemoryBackend(__FUNCTION__); + $cache = new MemoryBackend(new MemoryTagBackend(), __FUNCTION__); $cache->set($name, FALSE); $storage = $this->getMock('Drupal\Core\Config\StorageInterface'); diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php index 2f8c233..4e0c892 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityManagerTest.php @@ -66,6 +66,13 @@ class EntityManagerTest extends UnitTestCase { protected $cache; /** + * The cache tag backend to use. + * + * @var \Drupal\Core\Cache\CacheTagBackendInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $cacheTag; + + /** * The language manager. * * @var \Drupal\Core\Language\LanguageManager|\PHPUnit_Framework_MockObject_MockObject @@ -111,6 +118,8 @@ protected function setUp() { $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + $this->cacheTag = $this->getMock('Drupal\Core\Cache\CacheTagBackendInterface'); + $this->languageManager = $this->getMockBuilder('Drupal\Core\Language\LanguageManager') ->disableOriginalConstructor() ->getMock(); @@ -122,7 +131,7 @@ protected function setUp() { $this->formBuilder = $this->getMock('Drupal\Core\Form\FormBuilderInterface'); - $this->container = $this->getContainerWithCacheBins($this->cache); + $this->container = $this->getContainerWithCacheTags($this->cacheTag); $this->discovery = $this->getMock('Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface'); } @@ -607,7 +616,7 @@ protected function setUpEntityWithFieldDefinition($custom_invoke_all = FALSE, $f */ public function testClearCachedFieldDefinitions() { $this->setUpEntityManager(); - $this->cache->expects($this->once()) + $this->cacheTag->expects($this->once()) ->method('deleteTags') ->with(array('entity_field_info' => TRUE)); diff --git a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php index 94df741..54984d1 100644 --- a/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Extension/ThemeHandlerTest.php @@ -45,6 +45,13 @@ class ThemeHandlerTest extends UnitTestCase { protected $cacheBackend; /** + * The mocked cache tag service. + * + * @var \Drupal\Core\Cache\CacheTagBackendInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $cacheTag; + + /** * The mocked config factory. * * @var \Drupal\Core\Config\ConfigFactoryInterface|\PHPUnit_Framework_MockObject_MockObject @@ -97,6 +104,7 @@ protected function setUp() { $this->configFactory = $this->getConfigFactoryStub(array('system.theme' => array(), 'system.theme.disabled' => array())); $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + $this->cacheTag = $this->getMock('Drupal\Core\Cache\CacheTagBackendInterface'); $this->infoParser = $this->getMock('Drupal\Core\Extension\InfoParserInterface'); $this->configInstaller = $this->getMock('Drupal\Core\Config\ConfigInstallerInterface'); $this->routeBuilder = $this->getMockBuilder('Drupal\Core\Routing\RouteBuilder') @@ -107,7 +115,7 @@ protected function setUp() { ->getMock(); $this->themeHandler = new TestThemeHandler($this->configFactory, $this->moduleHandler, $this->cacheBackend, $this->infoParser, $this->configInstaller, $this->routeBuilder, $this->extensionDiscovery); - $this->getContainerWithCacheBins($this->cacheBackend); + $this->getContainerWithCacheTags($this->cacheTag); } /** diff --git a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php index bd1efc2..6464d2f 100644 --- a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php @@ -186,18 +186,23 @@ public function testDefaultPluginManagerWithFilledCache() { */ public function testCacheClearWithTags() { $cid = $this->randomName(); - $cache_backend = $this->getMockBuilder('Drupal\Core\Cache\MemoryBackend') + $memory_tag = $this->getMockBuilder('Drupal\Core\Cache\MemoryTagBackend') ->disableOriginalConstructor() ->getMock(); - $cache_backend + $memory_tag ->expects($this->once()) ->method('deleteTags') ->with(array('tag' => TRUE)); + + $cache_backend = $this->getMockBuilder('Drupal\Core\Cache\MemoryBackend') + ->setConstructorArgs(array($memory_tag)) + ->disableOriginalConstructor() + ->getMock(); $cache_backend ->expects($this->never()) ->method('deleteMultiple'); - $this->getContainerWithCacheBins($cache_backend); + $this->getContainerWithCacheTags($memory_tag); $language = new Language(array('id' => 'en')); $language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); diff --git a/core/tests/Drupal/Tests/UnitTestCase.php b/core/tests/Drupal/Tests/UnitTestCase.php index f64dca0..57680af 100644 --- a/core/tests/Drupal/Tests/UnitTestCase.php +++ b/core/tests/Drupal/Tests/UnitTestCase.php @@ -9,7 +9,7 @@ use Drupal\Component\Utility\Random; use Drupal\Component\Utility\String; -use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Cache\CacheTagBackendInterface; use Drupal\Core\DependencyInjection\ContainerBuilder; /** @@ -197,25 +197,24 @@ public function getStringTranslationStub() { /** * Sets up a container with cache bins. * - * @param \Drupal\Core\Cache\CacheBackendInterface $backend - * The cache backend to set up. + * @param \Drupal\Core\Cache\CacheTagBackendInterface $backend + * The cache tag to set up. * * @return \Symfony\Component\DependencyInjection\ContainerInterface|\PHPUnit_Framework_MockObject_MockObject * The container with the cache bins set up. */ - protected function getContainerWithCacheBins(CacheBackendInterface $backend) { + protected function getContainerWithCacheTags(CacheTagBackendInterface $backend) { $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); $container->expects($this->any()) ->method('getParameter') - ->with('cache_bins') - ->will($this->returnValue(array('cache.test' => 'test'))); + ->with('cache_tags') + ->will($this->returnValue(array('cache.tag.test' => 'test'))); $container->expects($this->any()) ->method('get') - ->with('cache.test') + ->with('cache.tag.test') ->will($this->returnValue($backend)); \Drupal::setContainer($container); return $container; } - }