diff --git a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php index 50c5d4d..f604980 100644 --- a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php +++ b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php @@ -99,7 +99,7 @@ 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 in the form: + * An array of cache items, keyed by cid. In the form: * @code * $items = array( * $cid => array( diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php index 28ce6e7..016fbdd 100644 --- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php +++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php @@ -205,33 +205,70 @@ protected function doSet($cid, $data, $expire, $tags) { * {@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(); - $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']); - $checksum = $this->checksumTags($flat_tags); - $fields = array( - 'cid' => $cid, - 'data' => is_string($item['data']) ? $item['data'] : serialize($item['data']), - 'expire' => $item['expire'], - 'created' => REQUEST_TIME, - 'serialized' => is_string($item['data']) ? 0 : 1, - 'tags' => implode(' ', $flat_tags), - 'checksum_invalidations' => $checksum['invalidations'], - 'checksum_deletions' => $checksum['deletions'], - ); - $query->values($fields); - } - $query->execute(); + + try { + $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; + } } /** diff --git a/core/lib/Drupal/Core/Cache/MemoryBackend.php b/core/lib/Drupal/Core/Cache/MemoryBackend.php index ff4cf59..fb9e29c 100644 --- a/core/lib/Drupal/Core/Cache/MemoryBackend.php +++ b/core/lib/Drupal/Core/Cache/MemoryBackend.php @@ -205,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/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php index 0704ec7..da28fc4 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; /** @@ -301,17 +302,35 @@ public function testGetMultiple() { public function testSetMultiple() { $backend = $this->getCacheBackend(); - // Set numerous testing keys. + $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')); } /**