diff --git a/core/lib/Drupal/Core/Cache/BackendChain.php b/core/lib/Drupal/Core/Cache/BackendChain.php index 4d8ca059689b0f62935093f104afcf372ca12a0c..2ea11187e87de04068b73a1d1184812564fa3087 100644 --- a/core/lib/Drupal/Core/Cache/BackendChain.php +++ b/core/lib/Drupal/Core/Cache/BackendChain.php @@ -131,6 +131,15 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array } } + /** + * {@inheritdoc} + */ + public function setMultiple(array $items) { + foreach ($this->backends as $backend) { + $backend->setMultiple($items); + } + } + /** * Implements Drupal\Core\Cache\CacheBackendInterface::delete(). */ diff --git a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php index f0e9882ea465899ce24f67b60c14400df81272fd..f6049805f5edff5b921bb6f831a396fe0d57b29c 100644 --- a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php +++ b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php @@ -95,6 +95,26 @@ public function getMultiple(&$cids, $allow_invalid = FALSE); */ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()); + /** + * Store multiple items in the persistent cache. + * + * @param array $items + * An array of cache items, keyed by cid. In the form: + * @code + * $items = array( + * $cid => array( + * // Required, will be automatically serialized if not a string. + * 'data' => $data, + * // Optional, defaults to CacheBackendInterface::CACHE_PERMANENT. + * 'expire' => CacheBackendInterface::CACHE_PERMANENT, + * // (optional) The cache tags for this item, see CacheBackendInterface::set(). + * 'tags' => array(), + * ), + * ); + * @endcode + */ + public function setMultiple(array $items); + /** * Deletes an item from the cache. * diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php index 4c431dc23088daea9194d7e0a93d1f9fe1e1ebdb..3ae9eacf90dde6d487d6f3363ef5334603b44a69 100644 --- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php +++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php @@ -201,6 +201,78 @@ protected function doSet($cid, $data, $expire, $tags) { ->execute(); } + /** + * {@inheritdoc} + */ + public function setMultiple(array $items) { + $deleted_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::deletedTags', array()); + $invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array()); + + // Use a transaction so that the database can write the changes in a single + // commit. + $transaction = $this->connection->startTransaction(); + + try { + // Delete all items first so we can do one insert. Rather than mulitple + // merge queries. + $this->deleteMultiple(array_keys($items)); + + $query = $this->connection + ->insert($this->bin) + ->fields(array('cid', 'data', 'expire', 'created', 'serialized', 'tags', 'checksum_invalidations', 'checksum_deletions')); + + foreach ($items as $cid => $item) { + $item += array( + 'expire' => CacheBackendInterface::CACHE_PERMANENT, + 'tags' => array(), + ); + + $flat_tags = $this->flattenTags($item['tags']); + + // Remove tags that were already deleted or invalidated during this + // request from the static caches so that another deletion or + // invalidation can occur. + foreach ($flat_tags as $tag) { + if (isset($deleted_tags[$tag])) { + unset($deleted_tags[$tag]); + } + if (isset($invalidated_tags[$tag])) { + unset($invalidated_tags[$tag]); + } + } + + $checksum = $this->checksumTags($flat_tags); + + $fields = array( + 'cid' => $cid, + 'expire' => $item['expire'], + 'created' => REQUEST_TIME, + 'tags' => implode(' ', $flat_tags), + 'checksum_invalidations' => $checksum['invalidations'], + 'checksum_deletions' => $checksum['deletions'], + ); + + if (!is_string($item['data'])) { + $fields['data'] = serialize($item['data']); + $fields['serialized'] = 1; + } + else { + $fields['data'] = $item['data']; + $fields['serialized'] = 0; + } + + $query->values($fields); + } + + $query->execute(); + } + catch (\Exception $e) { + $transaction->rollback(); + // @todo Log something here or just re throw? + throw $e; + } + } + /** * Implements Drupal\Core\Cache\CacheBackendInterface::delete(). */ diff --git a/core/lib/Drupal/Core/Cache/MemoryBackend.php b/core/lib/Drupal/Core/Cache/MemoryBackend.php index c4e16f73cae72d47f610bf16bb8032beda6a1e67..fb9e29c37dcdf56affb81a73a14512ccb558a0f0 100644 --- a/core/lib/Drupal/Core/Cache/MemoryBackend.php +++ b/core/lib/Drupal/Core/Cache/MemoryBackend.php @@ -106,6 +106,15 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array ); } + /** + * {@inheritdoc} + */ + public function setMultiple(array $items = array()) { + foreach ($items as $cid => $item) { + $this->set($cid, $item['data'], isset($item['expire']) ? $item['expire'] : CacheBackendInterface::CACHE_PERMANENT, isset($item['tags']) ? $item['tags'] : array()); + } + } + /** * Implements Drupal\Core\Cache\CacheBackendInterface::delete(). */ @@ -196,11 +205,11 @@ protected function flattenTags(array $tags) { foreach ($tags as $namespace => $values) { if (is_array($values)) { foreach ($values as $value) { - $flat_tags["$namespace:$value"] = "$namespace:$value"; + $flat_tags[] = "$namespace:$value"; } } else { - $flat_tags["$namespace:$values"] = "$namespace:$values"; + $flat_tags[] = "$namespace:$values"; } } return $flat_tags; diff --git a/core/lib/Drupal/Core/Cache/NullBackend.php b/core/lib/Drupal/Core/Cache/NullBackend.php index 94a426d87df37080551b60318c0f6b750ce4ad15..2d5664d32741b6f768192f1fd4e1c8454bffd650 100644 --- a/core/lib/Drupal/Core/Cache/NullBackend.php +++ b/core/lib/Drupal/Core/Cache/NullBackend.php @@ -49,6 +49,11 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) { */ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {} + /** + * {@inheritdoc} + */ + public function setMultiple(array $items = array()) {} + /** * Implements Drupal\Core\Cache\CacheBackendInterface::delete(). */ diff --git a/core/lib/Drupal/Core/Config/CachedStorage.php b/core/lib/Drupal/Core/Config/CachedStorage.php index 08dc5828f261c526b39bd0d57aa966df03ff56c1..a5d11fd03e39a1a07e774a9b57f992423228c8bf 100644 --- a/core/lib/Drupal/Core/Config/CachedStorage.php +++ b/core/lib/Drupal/Core/Config/CachedStorage.php @@ -93,9 +93,12 @@ public function readMultiple(array $names) { $list = $this->storage->readMultiple($names); // Cache configuration objects that were loaded from the storage, cache // missing configuration objects as an explicit FALSE. + $items = array(); foreach ($names as $name) { - $this->cache->set($name, isset($list[$name]) ? $list[$name] : FALSE); + $items[$name] = array('data' => isset($list[$name]) ? $list[$name] : FALSE); } + + $this->cache->setMultiple($items); } // Add the configuration objects from the cache to the list. diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php index d4066b097d4a69f916b1bb5849aec975a14938e3..feb54712f61f5e7f3eaf07f10389fc6d58bd5d35 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php @@ -237,7 +237,7 @@ public function testBlockViewBuilderAlter() { $cache_entry = $this->container->get('cache.render')->get($cid); $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.'); $expected_flattened_tags = array('content:1', 'block_view:1', 'block:test_block', 'theme:stark', 'block_plugin:test_cache'); - $this->assertIdentical($cache_entry->tags, array_combine($expected_flattened_tags, $expected_flattened_tags)); //, 'The block render element has been cached with the expected cache tags.'); + $this->assertIdentical($cache_entry->tags, $expected_flattened_tags); //, 'The block render element has been cached with the expected cache tags.'); $this->container->get('cache.render')->delete($cid); // Advanced: cached block, but an alter hook adds an additional cache tag. @@ -251,7 +251,7 @@ public function testBlockViewBuilderAlter() { $cache_entry = $this->container->get('cache.render')->get($cid); $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.'); $expected_flattened_tags = array('content:1', 'block_view:1', 'block:test_block', 'theme:stark', 'block_plugin:test_cache', $alter_add_tag . ':1'); - $this->assertIdentical($cache_entry->tags, array_combine($expected_flattened_tags, $expected_flattened_tags)); //, 'The block render element has been cached with the expected cache tags.'); + $this->assertIdentical($cache_entry->tags, $expected_flattened_tags); //, 'The block render element has been cached with the expected cache tags.'); $this->container->get('cache.render')->delete($cid); // Advanced: cached block, but an alter hook adds a #pre_render callback to 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 e5588167c10c6ba022b3350120d7f37444469ae6..9002314142a08ed889e91a48622588faa472e9b6 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php +++ b/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php @@ -8,6 +8,7 @@ namespace Drupal\system\Tests\Cache; use Drupal\Core\Cache\Cache; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\simpletest\DrupalUnitTestBase; /** @@ -295,6 +296,43 @@ public function testGetMultiple() { $this->assertFalse(in_array('test19', $cids), "Added cache id test19 is not in cids array."); } + /** + * Tests \Drupal\Core\Cache\CacheBackendInterface::setMultiple(). + */ + public function testSetMultiple() { + $backend = $this->getCacheBackend(); + + $future_expiration = REQUEST_TIME + 100; + + // Set multiple testing keys. + $backend->set('cid_1', 'Some other value'); + $items = array( + 'cid_1' => array('data' => 1), + 'cid_2' => array('data' => 2), + 'cid_3' => array('data' => array(1, 2)), + 'cid_4' => array('data' => 1, 'expire' => $future_expiration), + 'cid_5' => array('data' => 1, 'tags' => array('test' => array('a', 'b'))), + ); + $backend->setMultiple($items); + $cids = array_keys($items); + $cached = $backend->getMultiple($cids); + + $this->assertEqual($cached['cid_1']->data, $items['cid_1']['data'], 'Over-written cache item set correctly.'); + $this->assertEqual($cached['cid_1']->expire, CacheBackendInterface::CACHE_PERMANENT, 'Cache expiration defaults to permanent.'); + + $this->assertEqual($cached['cid_2']->data, $items['cid_2']['data'], 'New cache item set correctly.'); + $this->assertEqual($cached['cid_2']->expire, CacheBackendInterface::CACHE_PERMANENT, 'Cache expiration defaults to permanent.'); + + $this->assertEqual($cached['cid_3']->data, $items['cid_3']['data'], 'New cache item with serialized data set correctly.'); + $this->assertEqual($cached['cid_3']->expire, CacheBackendInterface::CACHE_PERMANENT, 'Cache expiration defaults to permanent.'); + + $this->assertEqual($cached['cid_4']->data, $items['cid_4']['data'], 'New cache item set correctly.'); + $this->assertEqual($cached['cid_4']->expire, $future_expiration, 'Cache expiration has been correctly set.'); + + $this->assertEqual($cached['cid_5']->data, $items['cid_5']['data'], 'New cache item set correctly.'); + $this->assertEqual($cached['cid_5']->tags, array('test:a', 'test:b')); + } + /** * Tests Drupal\Core\Cache\CacheBackendInterface::isEmpty(). */