diff --git a/core/core.services.yml b/core/core.services.yml index 65882af..eb9c6de 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -124,10 +124,11 @@ services: factory_class: Drupal\Component\Utility\Settings factory_method: getSingleton state: - class: Drupal\Core\KeyValueStore\KeyValueStoreInterface - factory_method: get - factory_service: keyvalue - arguments: [state] + class: Drupal\Core\KeyValueStore\KeyValueCacheDecorator + arguments: ['@cache.cache', '@lock', '@keyvalue', state] + tags: + - { name: needs_destruction } + - { name: persist } queue: class: Drupal\Core\Queue\QueueFactory arguments: ['@settings'] diff --git a/core/lib/Drupal/Core/KeyValueStore/KeyValueCacheDecorator.php b/core/lib/Drupal/Core/KeyValueStore/KeyValueCacheDecorator.php new file mode 100644 index 0000000..8d6c887 --- /dev/null +++ b/core/lib/Drupal/Core/KeyValueStore/KeyValueCacheDecorator.php @@ -0,0 +1,142 @@ +keyValueStore = $key_value_factory->get($collection); + } + + /** + * {@inheritdoc} + */ + protected function resolveCacheMiss($offset) { + $this->storage[$offset] = $this->keyValueStore->get($offset); + $this->persist($offset); + return $this->storage[$offset]; + } + + /** + * {@inheritdoc} + */ + public function delete($key) { + parent::delete($key); + $this->keyValueStore->delete($key); + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple(array $keys) { + foreach ($keys as $key) { + $this->delete($key); + } + $this->keyValueStore->deleteMultiple($keys); + } + + /** + * {@inheritdoc} + */ + public function getAll() { + // Caching this would mean that the whole store is added to the cache, + // this is expected to be a non-frequent operation that is not worth to be + // loaded from cache. + return $this->keyValueStore->getAll(); + } + + /** + * {@inheritdoc} + */ + public function getCollectionName() { + return $this->keyValueStore->getCollectionName(); + } + + /** + * {@inheritdoc} + */ + public function getMultiple(array $keys) { + $values = array(); + foreach ($keys as $key) { + $value = $this->get($key); + // Only return keys with a value. + if ($value !== NULL) { + $values[$key] = $value; + } + } + return $values; + } + + /** + * {@inheritdoc} + */ + public function setIfNotExists($key, $value) { + if ($this->keyValueStore->setIfNotExists($key, $value)) { + $this->set($key, $value); + return TRUE; + } + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function setMultiple(array $data) { + $this->keyValueStore->setMultiple($data); + foreach ($data as $key => $value) { + parent::set($key, $value); + $this->persist($key); + } + } + + /** + * {@inheritdoc} + */ + public function set($key, $value) { + $this->keyValueStore->set($key, $value); + parent::set($key, $value); + $this->persist($key); + } + + /** + * {@inheritdoc} + */ + public function deleteAll() { + $this->keyValueStore->deleteAll(); + $this->clear(); + } + +} diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php index 91925e1..855e3cb 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -1028,6 +1028,15 @@ protected function tearDown() { // which means they may need to access its filesystem and database. drupal_static_reset(); + if (($container = \Drupal::getContainer()) && $container->has('state')) { + $captured_emails = \Drupal::state()->get('system.test_email_collector') ?: array(); + $emailCount = count($captured_emails); + if ($emailCount) { + $message = format_plural($emailCount, '1 e-mail was sent during this test.', '@count e-mails were sent during this test.'); + $this->pass($message, t('E-mail')); + } + } + // Ensure that TestBase::changeDatabasePrefix() has run and TestBase::$setup // was not tricked into TRUE, since the following code would delete the // entire parent site otherwise. @@ -1049,14 +1058,6 @@ protected function tearDown() { // In case a fatal error occurred that was not in the test process read the // log to pick up any fatal errors. simpletest_log_read($this->testId, $this->databasePrefix, get_class($this), TRUE); - if (($container = drupal_container()) && $container->has('keyvalue')) { - $captured_emails = \Drupal::state()->get('system.test_email_collector') ?: array(); - $emailCount = count($captured_emails); - if ($emailCount) { - $message = format_plural($emailCount, '1 e-mail was sent during this test.', '@count e-mails were sent during this test.'); - $this->pass($message, t('E-mail')); - } - } // Delete temporary files directory. file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10), array($this, 'filePreDeleteCallback')); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 7b9302a..29c2f7b 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -958,7 +958,8 @@ protected function refreshVariables() { $conf = variable_initialize(); // Clear the tag cache. drupal_static_reset('Drupal\Core\Cache\CacheBackendInterface::tagCache'); - drupal_container()->get('config.factory')->reset(); + \Drupal::service('config.factory')->reset(); + \Drupal::state()->reset(); } /** diff --git a/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/CacheDecoratorTest.php b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/CacheDecoratorTest.php new file mode 100644 index 0000000..08f0870 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/KeyValueStore/CacheDecoratorTest.php @@ -0,0 +1,87 @@ + 'Key value cache decorator', + 'description' => 'Tests the key value cache decorator.', + 'group' => 'Key-value store', + ); + } + + protected function setUp() { + parent::setUp(); + $this->container + ->register('keyvalue.memory', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory'); + global $conf; + $conf['keyvalue_default'] = 'keyvalue.memory'; + $this->cache = new MemoryBackend('bin'); + } + + /** + * Tests that values are cached. + */ + public function testCache() { + $stores = $this->createStorage(); + $values = array(); + // Set the value and test that it is correctly returned. + foreach ($this->collections as $i => $collection) { + $stores[$i]->set('key', $this->objects[$i]); + $this->assertEqual($stores[$i]->get('key'), $this->objects[$i]); + // Destruct the class to have it write the cache. + $stores[$i]->destruct(); + + // Delete the value from the key value storage. + $this->container->get($this->factory)->get($collection)->delete('key'); + } + + // Create new objects. + $stores = $this->createStorage(); + + // Verify that we get the cached state as we have not notified the decorator + // about the deletion. + foreach ($this->collections as $i => $collection) { + $this->assertEqual($stores[$i]->get('key'), $this->objects[$i]); + + // Reset the cache and make sure the value was updated. + $stores[$i]->clear(); + $this->assertNull($stores[$i]->get('key')); + } + } + + /** + * {@inheritdoc} + */ + protected function createStorage() { + $stores = array(); + // Prepare the memory key value storages and decorated ones. + foreach ($this->collections as $i => $collection) { + $stores[$i] = new KeyValueCacheDecorator($this->cache, new NullLockBackend(), $this->container->get($this->factory), $collection); + } + + return $stores; + } + +}