diff -u b/core/includes/bootstrap.inc b/core/includes/bootstrap.inc --- b/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -2407,7 +2407,8 @@ * @param Symfony\Component\DependencyInjection\Container $new_container * A new container instance to replace the current. * @param bool $rebuild - * (optional) Internal use only. Whether to build a new minimal container. + * (optional) Internal use only. Whether to enforce a rebuild of the container. + * Used by the testing framework to inject a fresh container for unit tests. * * @return Symfony\Component\DependencyInjection\Container * The instance of the Container used to set up and maintain object @@ -2418,8 +2419,11 @@ // which to reinitialize the stored objects, so a drupal_static_reset() call // would leave Drupal in a nonfunctional state. static $container = NULL; - if (isset($new_container) || $rebuild) { - $container = $rebuild ? NULL : $new_container; + if ($rebuild) { + $container = NULL; + } + if (isset($new_container)) { + $container = $new_container; } if (!isset($container)) { // Return a ContainerBuilder instance with the bare essentials needed for any @@ -2428,27 +2432,34 @@ // requests. $container = new ContainerBuilder(); - $container->register('config.cachedstorage.delegate', 'Drupal\Core\Config\FileStorage') - ->addArgument(config_get_config_directory()); - $container->register('config.cachedstorage.cache') + // Register active configuration storage. + $container + ->register('config.cachedstorage.storage', 'Drupal\Core\Config\FileStorage') + ->addArgument(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY)); + // @todo Replace this with a cache.factory service plus 'config' argument. + $container + ->register('cache.config') ->setFactoryClass('Drupal\Core\Cache\CacheFactory') ->setFactoryMethod('get') ->addArgument('config'); - $container->register('config.storage.staging', 'Drupal\Core\Config\FileStorage') - ->addArgument(config_get_config_directory(CONFIG_STAGING_DIRECTORY)); - $container->register('config.storage', 'Drupal\Core\Config\CachedStorage') - ->addArgument(new Reference('config.cachedstorage.delegate')) - ->addArgument(new Reference('config.cachedstorage.cache')); + $container + ->register('config.storage', 'Drupal\Core\Config\CachedStorage') + ->addArgument(new Reference('config.cachedstorage.storage')) + ->addArgument(new Reference('cache.config')); + + // Register configuration object factory. $container->register('config.subscriber.globalconf', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber'); $container->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher') ->addMethodCall('addSubscriber', array(new Reference('config.subscriber.globalconf'))); - - // Register configuration object factory. $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory') ->addArgument(new Reference('config.storage')) ->addArgument(new Reference('dispatcher')); + // Register staging configuration storage. + $container + ->register('config.storage.staging', 'Drupal\Core\Config\FileStorage') + ->addArgument(config_get_config_directory(CONFIG_STAGING_DIRECTORY)); } return $container; } diff -u b/core/includes/cache.inc b/core/includes/cache.inc --- b/core/includes/cache.inc +++ b/core/includes/cache.inc @@ -1,12 +1,12 @@ register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher'); @@ -1093,7 +1093,7 @@ // We have valid configuration directories in settings.php. // Reset the service container, so the config.storage service will use the - // actual cached storage for installing configuration. + // actual active storage for installing configuration. drupal_container(NULL, TRUE); // Indicate that the settings file has been verified, and check the database diff -u b/core/includes/install.inc b/core/includes/install.inc --- b/core/includes/install.inc +++ b/core/includes/install.inc @@ -313,10 +313,9 @@ if (!isset($config_directories[$type])) { return FALSE; } - // config_get_config_directory() throws an exception when the $type passed - // it doesn't exist yet in $config_directories. This can happen if there is - // a prepared settings.php that defines $config_directories already and the - // directories do not exist yet. + // config_get_config_directory() throws an exception when the passed $type + // does not exist in $config_directories. This can happen if there is a + // prepared settings.php that defines $config_directories already. try { $config_directory = config_get_config_directory($type); if (is_dir($config_directory) && is_writable($config_directory)) { diff -u b/core/lib/Drupal/Core/Cache/CacheFactory.php b/core/lib/Drupal/Core/Cache/CacheFactory.php --- b/core/lib/Drupal/Core/Cache/CacheFactory.php +++ b/core/lib/Drupal/Core/Cache/CacheFactory.php @@ -2,10 +2,47 @@ +/** + * @file + * Contains Drupal\Core\Cache\CacheFactory. + */ + namespace Drupal\Core\Cache; +/** + * Defines the cache backend factory. + */ class CacheFactory { + + /** + * Instantiates a cache backend class for a given cache bin. + * + * By default, this returns an instance of the + * Drupal\Core\Cache\DatabaseBackend class. + * + * Classes implementing Drupal\Core\Cache\CacheBackendInterface can register + * themselves both as a default implementation and for specific bins. + * + * @param string $bin + * The cache bin for which a cache backend object should be returned. + * + * @return Drupal\Core\Cache\CacheBackendInterface + * The cache backend object associated with the specified bin. + */ static function get($bin) { - $cache_backends = cache_get_backends(); - $class = isset($cache_backends[$bin]) ? $cache_backends[$bin] : $cache_backends['cache']; + // @todo Improve how cache backend classes are defined. Cannot be + // configuration, since e.g. the CachedStorage config storage controller + // requires the definition in its constructor already. + global $conf; + $cache_backends = isset($conf['cache_classes']) ? $conf['cache_classes'] : array(); + + // Check whether there is a custom class defined for the requested bin and + // default to the DatabaseBackend otherwise. + if (isset($cache_backends[$bin])) { + $class = $cache_backends[$bin]; + } + else { + $class = 'Drupal\Core\Cache\DatabaseBackend'; + } return new $class($bin); } + } diff -u b/core/lib/Drupal/Core/Config/CachedStorage.php b/core/lib/Drupal/Core/Config/CachedStorage.php --- b/core/lib/Drupal/Core/Config/CachedStorage.php +++ b/core/lib/Drupal/Core/Config/CachedStorage.php @@ -2,7 +2,7 @@ /** * @file - * Contains Drupal\Core\Config\CacheStorage. + * Contains Drupal\Core\Config\CachedStorage. */ namespace Drupal\Core\Config; @@ -12,18 +12,18 @@ /** * Defines the cached storage controller. * - * The class gets another storage and a cache backend injected. It tries to - * read from the cache and if it misses, delegates the read. It also handles - * cache invalidation. + * The class gets another storage and a cache backend injected. It reads from + * the cache and delegates the read to the storage on a cache miss. It also + * handles cache invalidation. */ class CachedStorage implements StorageInterface { /** - * The storage to be cached. + * The configuration storage to be cached. * * @var Drupal\Core\Config\StorageInterface */ - protected $delegate; + protected $storage; /** * The instantiated Cache backend. @@ -33,10 +33,15 @@ protected $cache; /** - * Implements Drupal\Core\Config\StorageInterface::__construct(). + * Constructs a new CachedStorage controller. + * + * @param Drupal\Core\Config\StorageInterface $storage + * A configuration storage controller to be cached. + * @param Drupal\Core\Cache\CacheBackendInterface $cache + * A cache backend instance to use for caching. */ - public function __construct(StorageInterface $delegate, CacheBackendInterface $cache) { - $this->delegate = $delegate; + public function __construct(StorageInterface $storage, CacheBackendInterface $cache) { + $this->storage = $storage; $this->cache = $cache; } @@ -44,7 +49,9 @@ * Implements Drupal\Core\Config\StorageInterface::exists(). */ public function exists($name) { - return $this->delegate->exists($name); + // A single storage lookup is faster than a cache lookup and possibly + // subsequent storage lookup. + return $this->storage->exists($name); } /** @@ -52,18 +59,26 @@ */ public function read($name) { if ($cache = $this->cache->get($name)) { - return $cache->data; + // The cache backend supports primitive data types, but only an array + // represents valid config object data. + if (is_array($cache->data)) { + return $cache->data; + } + } + // Read from the storage on a cache miss and cache the data, if any. + $data = $this->storage->read($name); + if ($data !== FALSE) { + $this->cache->set($name, $data, CacheBackendInterface::CACHE_PERMANENT, array('config' => array($name))); } - return $this->delegate->read($name); + return $data; } /** * Implements Drupal\Core\Config\StorageInterface::write(). */ public function write($name, array $data) { - $this->cache->set($name, $data, CacheBackendInterface::CACHE_PERMANENT, array('config' => array($name))); - $this->delegate->write($name, $data); - return TRUE; + $this->cache->delete($name); + return $this->storage->write($name, $data); } /** @@ -71,8 +86,7 @@ */ public function delete($name) { $this->cache->delete($name); - $this->delegate->delete($name); - return TRUE; + return $this->storage->delete($name); } /** @@ -81,26 +95,21 @@ public function rename($name, $new_name) { $this->cache->delete($name); $this->cache->delete($new_name); - $this->delegate->rename($name, $new_name); - return TRUE; + return $this->storage->rename($name, $new_name); } /** * Implements Drupal\Core\Config\StorageInterface::encode(). */ public static function encode($data) { - return serialize($data); + return $this->storage->encode($data); } /** * Implements Drupal\Core\Config\StorageInterface::decode(). - * - * @throws ErrorException - * unserialize() triggers E_NOTICE if the string cannot be unserialized. */ public static function decode($raw) { - $data = @unserialize($raw); - return is_array($data) ? $data : FALSE; + return $this->storage->decode($raw); } /** @@ -110,5 +119,5 @@ */ public function listAll($prefix = '') { - return $this->delegate->listAll($prefix); + return $this->storage->listAll($prefix); } } diff -u /dev/null b/core/lib/Drupal/Core/Config/DatabaseStorage.php --- /dev/null +++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php @@ -0,0 +1,163 @@ +connection = $connection; + $this->table = $table; + $this->options = $options; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::exists(). + */ + public function exists($name) { + return (bool) $this->connection->queryRange('SELECT 1 FROM {' . $this->table . '} WHERE name = :name', 0, 1, array( + ':name' => $name, + ), $this->options)->fetchField(); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::read(). + * + * @throws PDOException + * @throws Drupal\Core\Database\DatabaseExceptionWrapper + * Only thrown in case $this->options['throw_exception'] is TRUE. + */ + public function read($name) { + $data = FALSE; + // There are situations, like in the installer, where we may attempt a + // read without actually having the database available. In this case, + // catch the exception and just return an empty array so the caller can + // handle it if need be. + try { + $raw = $this->connection->query('SELECT data FROM {' . $this->table . '} WHERE name = :name', array(':name' => $name), $this->options)->fetchField(); + if ($raw !== FALSE) { + $data = $this->decode($raw); + } + } + catch (Exception $e) { + } + return $data; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::write(). + * + * @throws PDOException + * + * @todo Ignore slave targets for data manipulation operations. + */ + public function write($name, array $data) { + $data = $this->encode($data); + $options = array('return' => Database::RETURN_AFFECTED) + $this->options; + return (bool) $this->connection->merge($this->table, $options) + ->key(array('name' => $name)) + ->fields(array('data' => $data)) + ->execute(); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::delete(). + * + * @throws PDOException + * + * @todo Ignore slave targets for data manipulation operations. + */ + public function delete($name) { + $options = array('return' => Database::RETURN_AFFECTED) + $this->options; + return (bool) $this->connection->delete($this->table, $options) + ->condition('name', $name) + ->execute(); + } + + + /** + * Implements Drupal\Core\Config\StorageInterface::rename(). + * + * @throws PDOException + */ + public function rename($name, $new_name) { + $options = array('return' => Database::RETURN_AFFECTED) + $this->options; + return (bool) $this->connection->update($this->table, $options) + ->fields(array('name' => $new_name)) + ->condition('name', $name) + ->execute(); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::encode(). + */ + public static function encode($data) { + return serialize($data); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::decode(). + * + * @throws ErrorException + * unserialize() triggers E_NOTICE if the string cannot be unserialized. + */ + public static function decode($raw) { + $data = @unserialize($raw); + return is_array($data) ? $data : FALSE; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::listAll(). + * + * @throws PDOException + * @throws Drupal\Core\Database\DatabaseExceptionWrapper + * Only thrown in case $this->options['throw_exception'] is TRUE. + */ + public function listAll($prefix = '') { + return $this->connection->query('SELECT name FROM {' . $this->table . '} WHERE name LIKE :name', array( + ':name' => db_like($prefix) . '%', + ), $this->options)->fetchCol(); + } +} diff -u b/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php --- b/core/lib/Drupal/Core/Config/FileStorage.php +++ b/core/lib/Drupal/Core/Config/FileStorage.php @@ -22,7 +22,10 @@ protected $directory = ''; /** - * Implements Drupal\Core\Config\StorageInterface::__construct(). + * Constructs a new FileStorage controller. + * + * @param string $directory + * A directory path to use for reading and writing of configuration files. */ public function __construct($directory) { $this->directory = $directory; diff -u b/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php --- b/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php @@ -200,7 +200,7 @@ ); // Encode and write, and reload and decode the configuration data. - $filestorage = new FileStorage(config_get_config_directory()); + $filestorage = new FileStorage($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]); $filestorage->write($name, $config_data); $config_parsed = $filestorage->read($name); diff -u /dev/null b/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php --- /dev/null +++ b/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php @@ -0,0 +1,72 @@ + 'DatabaseStorage controller operations', + 'description' => 'Tests DatabaseStorage controller operations.', + 'group' => 'Configuration', + ); + } + + function setUp() { + parent::setUp(); + + $schema['config'] = array( + 'description' => 'Default active store for the configuration system.', + 'fields' => array( + 'name' => array( + 'description' => 'The identifier for the configuration entry, such as module.example (the name of the file, minus the file extension).', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'data' => array( + 'description' => 'The raw data for this configuration entry.', + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + 'translatable' => TRUE, + ), + ), + 'primary key' => array('name'), + ); + db_create_table('config', $schema['config']); + + $this->storage = new DatabaseStorage($this->container->get('database'), 'config'); + $this->invalidStorage = new DatabaseStorage($this->container->get('database'), 'invalid'); + + // ::listAll() verifications require other configuration data to exist. + $this->storage->write('system.performance', array()); + } + + protected function read($name) { + $data = db_query('SELECT data FROM {config} WHERE name = :name', array(':name' => $name))->fetchField(); + return unserialize($data); + } + + protected function insert($name, $data) { + db_insert('config')->fields(array('name' => $name, 'data' => $data))->execute(); + } + + protected function update($name, $data) { + db_update('config')->fields(array('data' => $data))->condition('name', $name)->execute(); + } + + protected function delete($name) { + db_delete('config')->condition('name', $name)->execute(); + } +} diff -u b/core/modules/config/lib/Drupal/config/Tests/Storage/FileStorageTest.php b/core/modules/config/lib/Drupal/config/Tests/Storage/FileStorageTest.php --- b/core/modules/config/lib/Drupal/config/Tests/Storage/FileStorageTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/Storage/FileStorageTest.php @@ -24,7 +24,7 @@ function setUp() { parent::setUp(); - $this->storage = new FileStorage(config_get_config_directory()); + $this->storage = new FileStorage($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]); $this->invalidStorage = new FileStorage($this->configDirectories[CONFIG_ACTIVE_DIRECTORY] . '/nonexisting'); // FileStorage::listAll() requires other configuration data to exist. diff -u b/core/modules/entity/lib/Drupal/entity/Entity.php b/core/modules/entity/lib/Drupal/entity/Entity.php --- b/core/modules/entity/lib/Drupal/entity/Entity.php +++ b/core/modules/entity/lib/Drupal/entity/Entity.php @@ -289,2 +289,3 @@ } + } diff -u b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php --- b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -727,8 +727,7 @@ } // Reset and create a new service container. - drupal_container(NULL, TRUE); - $this->container = drupal_container(); + $this->container = drupal_container(NULL, TRUE); $this->configDirectories = array(); include_once DRUPAL_ROOT . '/core/includes/install.inc'; diff -u b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php --- b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -674,6 +674,9 @@ // Symfony\Component\HttpKernel\handle(), this kernel needs manual booting // as it is not used to handle a request. $this->kernel->boot(); + // The DrupalKernel does not update the container in drupal_container(), but + // replaces it with a new object. We therefore need to replace the minimal + // boostrap container that has been set up by TestBase::prepareEnvironment(). $this->container = drupal_container(); // Reset/rebuild all data structures after enabling the modules. diff -u b/core/modules/system/system.install b/core/modules/system/system.install --- b/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -1550,7 +1550,7 @@ * Creates {cache_config} cache table for the new configuration system. */ function system_update_8003() { - $cache_update = array( + $spec = array( 'description' => 'Cache table for configuration data.', 'fields' => array( 'cid' => array( @@ -1603,7 +1603,7 @@ ), 'primary key' => array('cid'), ); - db_create_table('cache_config', $cache_update); + db_create_table('cache_config', $spec); } /**