Commit 0ae4c3c3 authored by catch's avatar catch

Issue #2098119 by beejeebus, alexpott, chx: Replace config context system with...

Issue #2098119 by beejeebus, alexpott, chx: Replace config context system with baked-in locale support and single-event based overrides.
parent fa17da86
......@@ -75,25 +75,12 @@ services:
arguments: ['@config.cachedstorage.storage', '@cache.config']
tags:
- { name: persist }
config.context.factory:
class: Drupal\Core\Config\Context\ConfigContextFactory
arguments: ['@event_dispatcher', '@uuid']
config.context:
class: Drupal\Core\Config\Context\ContextInterface
tags:
- { name: persist }
factory_method: get
factory_service: config.context.factory
config.context.free:
class: Drupal\Core\Config\Context\ContextInterface
factory_method: get
factory_service: config.context.factory
arguments: [Drupal\Core\Config\Context\FreeConfigContext]
config.factory:
class: Drupal\Core\Config\ConfigFactory
tags:
- { name: persist }
arguments: ['@config.storage', '@config.context', '@config.typed']
- { name: event_subscriber }
arguments: ['@config.storage', '@event_dispatcher', '@config.typed']
config.storage.staging:
class: Drupal\Core\Config\FileStorage
factory_class: Drupal\Core\Config\FileStorageFactory
......@@ -617,7 +604,7 @@ services:
arguments: ['@module_handler']
date:
class: Drupal\Core\Datetime\Date
arguments: ['@entity.manager', '@language_manager', '@string_translation']
arguments: ['@entity.manager', '@language_manager', '@string_translation', '@config.factory']
feed.bridge.reader:
class: Drupal\Component\Bridge\ZfExtensionManagerSfContainer
calls:
......
......@@ -3,12 +3,13 @@
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\ConfigException;
use Drupal\Core\Language\Language;
use Drupal\Core\Config\ExtensionInstallStorage;
use Drupal\Core\Config\Context\FreeConfigContext;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Symfony\Component\Yaml\Dumper;
use Symfony\Component\EventDispatcher\EventDispatcher;
/**
* @file
......@@ -66,17 +67,18 @@ function ($value) use ($name) {
if (!empty($config_to_install)) {
$entity_manager = Drupal::service('entity.manager');
$config_factory = Drupal::service('config.factory');
$context = new FreeConfigContext(Drupal::service('event_dispatcher'), Drupal::service('uuid'));
$target_storage = Drupal::service('config.storage');
$typed_config = Drupal::service('config.typed');
$config_factory->enterContext($context);
$event_dispatcher = Drupal::service('event_dispatcher');
$config_factory->disableOverrides();
foreach ($config_to_install as $name) {
// Only import new config.
if ($target_storage->exists($name)) {
continue;
}
$new_config = new Config($name, $target_storage, $context, $typed_config);
$new_config = new Config($name, $target_storage, $event_dispatcher, $typed_config);
$data = $source_storage->read($name);
if ($data !== FALSE) {
$new_config->setData($data);
......@@ -90,11 +92,8 @@ function ($value) use ($name) {
else {
$new_config->save();
}
// Reset static cache on the config factory.
$config_factory->reset($name);
$config_factory->enableOverrides();
}
$config_factory->leaveContext();
}
}
......@@ -145,54 +144,6 @@ function config($name) {
return \Drupal::config($name);
}
/**
* Sets the config context on the config factory.
*
* This allows configuration objects to be created using special configuration
* contexts eg. global override free or locale using a user preferred language.
* Calling this function affects all subsequent calls to \Drupal::config() until
* config_context_leave() is called.
*
* @see config_context_leave()
* @see \Drupal\Core\Config\ConfigFactory
*
* @param string $context_name
* The name of the config context service on the container or a fully
* qualified class implementing \Drupal\Core\Config\Context\ContextInterface.
*
* @return \Drupal\Core\Config\Context\ContextInterface
* The configuration context object.
*/
function config_context_enter($context_name) {
$container = \Drupal::getContainer();
if ($container->has($context_name)) {
$context = $container->get($context_name);
}
elseif (class_exists($context_name) && in_array('Drupal\Core\Config\Context\ContextInterface', class_implements($context_name))) {
$context = $container
->get('config.context.factory')
->get($context_name);
}
else {
throw new ConfigException(sprintf('Unknown config context service or class: %s', $context_name));
}
$container
->get('config.factory')
->enterContext($context);
return $context;
}
/**
* Leaves the current config context returning to the previous context.
*
* @see config_context_enter()
* @see \Drupal\Core\Config\ConfigFactory
*/
function config_context_leave() {
\Drupal::service('config.factory')
->leaveContext();
}
/**
* Returns the entity type of a configuration object.
*
......
......@@ -378,13 +378,6 @@ function install_begin_request(&$install_state) {
$container->register('event_dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher');
$container->register('config.storage', 'Drupal\Core\Config\InstallStorage');
$container->register('config.context.factory', 'Drupal\Core\Config\Context\ConfigContextFactory')
->addArgument(new Reference('event_dispatcher'))
->addArgument(new Reference('uuid'));
$container->register('config.context', 'Drupal\Core\Config\Context\ContextInterface')
->setFactoryService(new Reference('config.context.factory'))
->setFactoryMethod('get');
$container->register('config.storage.schema', 'Drupal\Core\Config\Schema\SchemaStorage');
......@@ -395,7 +388,7 @@ function install_begin_request(&$install_state) {
$container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
->addArgument(new Reference('config.storage'))
->addArgument(new Reference('config.context'))
->addArgument(new Reference('event_dispatcher'))
->addArgument(new Reference('config.typed'));
// Register the 'language_manager' service.
......
......@@ -2564,7 +2564,7 @@ function menu_router_rebuild() {
function menu_router_build($save = FALSE) {
// Ensure that all configuration used to build the menu items are loaded
// without overrides.
config_context_enter('config.context.free');
\Drupal::configFactory()->disableOverrides();
// We need to manually call each module so that we can know which module
// a given item came from.
$callbacks = array();
......@@ -2579,8 +2579,8 @@ function menu_router_build($save = FALSE) {
}
// Alter the menu as defined in modules, keys are like user/%user.
drupal_alter('menu', $callbacks);
// Return to the original context before menu building.
config_context_leave();
// Re-enable configuration overrides.
\Drupal::configFactory()->enableOverrides();
foreach ($callbacks as $path => $router_item) {
// If the menu item is a default local task and incorrectly references a
// route, remove it.
......
......@@ -241,6 +241,19 @@ public static function config($name) {
return static::$container->get('config.factory')->get($name);
}
/**
* Retrieves the configuration factory.
*
* This is mostly used to change the override settings on the configuration
* factory. For example, changing the language, or turning all overrides on
* or off.
*
* @return \Drupal\Core\Config\ConfigFactory
*/
public static function configFactory() {
return static::$container->get('config.factory');
}
/**
* Returns a queue for the given queue name.
*
......
......@@ -10,11 +10,12 @@
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\String;
use Drupal\Core\Config\ConfigNameException;
use Drupal\Core\Config\Context\ContextInterface;
use Drupal\Core\Config\Schema\SchemaIncompleteException;
use Drupal\Core\TypedData\PrimitiveInterface;
use Drupal\Core\TypedData\Type\FloatInterface;
use Drupal\Core\TypedData\Type\IntegerInterface;
use Drupal\Core\Language\Language;
use Symfony\Component\EventDispatcher\EventDispatcher;
/**
* Defines the default configuration object.
......@@ -33,6 +34,20 @@ class Config {
*/
const MAX_NAME_LENGTH = 250;
/**
* An event dispatcher instance to use for configuration events.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcher
*/
protected $eventDispatcher;
/**
* The language object used to override configuration data.
*
* @var Drupal\Core\Language\Language
*/
protected $language;
/**
* The name of the configuration object.
*
......@@ -55,25 +70,35 @@ class Config {
protected $data;
/**
* The current runtime data ($data + $overrides from Config Context).
* The current runtime data.
*
* The configuration data from storage merged with language, module and
* settings overrides.
*
* @var array
*/
protected $overriddenData;
/**
* The storage used to load and save this configuration object.
* The current language overrides.
*
* @var \Drupal\Core\Config\StorageInterface
* @var array
*/
protected $storage;
protected $languageOverrides;
/**
* The configuration context used for this configuration object.
* The current module overrides.
*
* @var \Drupal\Core\Config\Context\ContextInterface
* @var array
*/
protected $context;
protected $moduleOverrides;
/**
* The storage used to load and save this configuration object.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $storage;
/**
* Whether the configuration object has already been loaded.
......@@ -104,28 +129,19 @@ class Config {
* @param \Drupal\Core\Config\StorageInterface $storage
* A storage controller object to use for reading and writing the
* configuration data.
* @param \Drupal\Core\Config\Context\ContextInterface $context
* The configuration context used for this configuration object.
* @param \Symfony\Component\EventDispatcher\EventDispatcher $event_dispatcher
* An event dispatcher instance to use for configuration events.
* @param \Drupal\Core\Config\TypedConfigManager $typed_config
* The typed configuration manager service.
* @param \Drupal\Core\Language\Language $language
* The language object used to override configuration data.
*/
public function __construct($name, StorageInterface $storage, ContextInterface $context, TypedConfigManager $typed_config) {
public function __construct($name, StorageInterface $storage, EventDispatcher $event_dispatcher, TypedConfigManager $typed_config, Language $language = NULL) {
$this->name = $name;
$this->storage = $storage;
$this->context = $context;
$this->eventDispatcher = $event_dispatcher;
$this->typedConfigManager = $typed_config;
}
/**
* Initializes a configuration object.
*
* @return \Drupal\Core\Config\Config
* The configuration object.
*/
public function init() {
$this->isLoaded = FALSE;
$this->notify('init');
return $this;
$this->language = $language;
}
/**
......@@ -139,10 +155,11 @@ public function init() {
*/
public function initWithData(array $data) {
$this->isLoaded = TRUE;
$this->settingsOverrides = array();
$this->languageOverrides = array();
$this->moduleOverrides = array();
$this->isNew = FALSE;
$this->notify('init');
$this->replaceData($data);
$this->notify('load');
return $this;
}
......@@ -295,7 +312,7 @@ protected function replaceData(array $data) {
}
/**
* Sets overridden data for this configuration object.
* Sets settings.php overrides for this configuration object.
*
* The overridden data only applies to this configuration object.
*
......@@ -305,8 +322,38 @@ protected function replaceData(array $data) {
* @return \Drupal\Core\Config\Config
* The configuration object.
*/
public function setOverride(array $data) {
$this->context->setOverrides($this->getName(), $data);
public function setSettingsOverride(array $data) {
$this->settingsOverrides = $data;
$this->resetOverriddenData();
return $this;
}
/**
* Sets module overrides for this configuration object.
*
* @param array $data
* The overridden values of the configuration data.
*
* @return \Drupal\Core\Config\Config
* The configuration object.
*/
public function setModuleOverride(array $data) {
$this->moduleOverrides = $data;
$this->resetOverriddenData();
return $this;
}
/**
* Sets language overrides for this configuration object.
*
* @param array $data
* The overridden values of the configuration data.
*
* @return \Drupal\Core\Config\Config
* The configuration object.
*/
public function setLanguageOverride(array $data) {
$this->languageOverrides = $data;
$this->resetOverriddenData();
return $this;
}
......@@ -314,16 +361,24 @@ public function setOverride(array $data) {
/**
* Sets the current data for this configuration object.
*
* Merges overridden configuration data into the original data.
* Configuration overrides operate at three distinct layers: language, modules
* and settings.php, with the last of these taking precedence. Overrides in
* settings.php take precedence over values provided by modules. Overrides
* provided by modules take precedence over language.
*
* @return \Drupal\Core\Config\Config
* The configuration object.
*/
protected function setOverriddenData() {
$this->overriddenData = $this->data;
$overrides = $this->context->getOverrides($this->getName());
if (is_array($overrides)) {
$this->overriddenData = NestedArray::mergeDeepArray(array($this->overriddenData, $overrides), TRUE);
if (isset($this->languageOverrides) && is_array($this->languageOverrides)) {
$this->overriddenData = NestedArray::mergeDeepArray(array($this->overriddenData, $this->languageOverrides), TRUE);
}
if (isset($this->moduleOverrides) && is_array($this->moduleOverrides)) {
$this->overriddenData = NestedArray::mergeDeepArray(array($this->overriddenData, $this->moduleOverrides), TRUE);
}
if (isset($this->settingsOverrides) && is_array($this->settingsOverrides)) {
$this->overriddenData = NestedArray::mergeDeepArray(array($this->overriddenData, $this->settingsOverrides), TRUE);
}
return $this;
}
......@@ -412,7 +467,6 @@ public function load() {
$this->isNew = FALSE;
$this->replaceData($data);
}
$this->notify('load');
$this->isLoaded = TRUE;
return $this;
}
......@@ -479,7 +533,7 @@ public function getStorage() {
* The configuration event name.
*/
protected function notify($config_event_name) {
$this->context->notify($config_event_name, $this);
$this->eventDispatcher->dispatch('config.' . $config_event_name, new ConfigEvent($this));
}
/**
......@@ -574,4 +628,27 @@ protected function castValue($key, $value) {
return $value;
}
/**
* Returns the language object for this Config object.
*
* @return \Drupal\Core\Language\Language
*/
public function getLanguage() {
return $this->language;
}
/**
* Gets the raw data without overrides.
*
* @return array
* The raw data.
*/
public function getRawData() {
if (!$this->isLoaded) {
$this->load();
}
return $this->data;
}
}
......@@ -2,7 +2,6 @@
namespace Drupal\Core\Config;
use Drupal\Core\Config\Context\ContextInterface;
use Symfony\Component\EventDispatcher\Event;
class ConfigEvent extends Event {
......@@ -14,24 +13,14 @@ class ConfigEvent extends Event {
*/
protected $config;
/**
* Configuration context object.
*
* @var \Drupal\Core\Config\Context\ContextInterface
*/
protected $context;
/**
* Constructs a configuration event object.
*
* @param \Drupal\Core\Config\Context\ContextInterface
* Configuration context object.
* @param \Drupal\Core\Config\Config
* (optional) Configuration object.
* Configuration object.
*/
public function __construct(ContextInterface $context, Config $config = NULL) {
public function __construct(Config $config) {
$this->config = $config;
$this->context = $context;
}
/**
......@@ -40,14 +29,5 @@ public function __construct(ContextInterface $context, Config $config = NULL) {
public function getConfig() {
return $this->config;
}
/**
* Gets configuration context object.
*
* @return \Drupal\Core\Config\Context\ContextInterface
* Configuration context.
*/
public function getContext() {
return $this->context;
}
}
......@@ -7,7 +7,6 @@
namespace Drupal\Core\Config;
use Drupal\Core\Config\Context\FreeConfigContext;
use Drupal\Core\Config\TypedConfigManager;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Lock\LockBackendInterface;
......@@ -54,13 +53,6 @@ class ConfigImporter {
*/
protected $eventDispatcher;
/**
* The configuration context.
*
* @var \Drupal\Core\Config\Context\ContextInterface
*/
protected $context;
/**
* The configuration factory.
*
......@@ -138,10 +130,6 @@ public function __construct(StorageComparerInterface $storage_comparer, EventDis
$this->uuidService = $uuid_service;
$this->typedConfigManager = $typed_config;
$this->processed = $this->storageComparer->getEmptyChangelist();
// Use an override free context for importing so that overrides to do not
// pollute the imported data. The context is hard coded to ensure this is
// the case.
$this->context = new FreeConfigContext($this->eventDispatcher, $this->uuidService);
}
/**
......@@ -235,7 +223,6 @@ public function import() {
// Ensure that the changes have been validated.
$this->validate();
$this->configFactory->enterContext($this->context);
if (!$this->lock->acquire(static::ID)) {
// Another process is synchronizing configuration.
throw new ConfigImporterException(sprintf('%s is already importing', static::ID));
......@@ -248,9 +235,6 @@ public function import() {
// The import is now complete.
$this->lock->release(static::ID);
$this->reset();
// Leave the context used during import and clear the ConfigFactory's
// static cache.
$this->configFactory->leaveContext()->reset();
}
return $this;
}
......@@ -278,7 +262,7 @@ public function validate() {
protected function importConfig() {
foreach (array('delete', 'create', 'update') as $op) {
foreach ($this->getUnprocessed($op) as $name) {
$config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context, $this->typedConfigManager);
$config = new Config($name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager);
if ($op == 'delete') {
$config->delete();
}
......@@ -311,11 +295,11 @@ protected function importInvokeOwner() {
// Validate the configuration object name before importing it.
// Config::validateName($name);
if ($entity_type = config_get_entity_type_by_name($name)) {
$old_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context, $this->typedConfigManager);
$old_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager);
$old_config->load();
$data = $this->storageComparer->getSourceStorage()->read($name);
$new_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context, $this->typedConfigManager);
$new_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager);
if ($data !== FALSE) {
$new_config->setData($data);
}
......
<?php
/**
* @file
* Contains \Drupal\Core\Config\ConfigModuleOverridesEvent.
*/
namespace Drupal\Core\Config;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Language\Language;
use Symfony\Component\EventDispatcher\Event;
/**
* Event object to allow configuration to be overridden by modules.
*/
class ConfigModuleOverridesEvent extends Event {
/**
* Configuration names.
*
* @var array
*/
protected $names;
/**
* Configuration overrides.
*
* @var array
*/
protected $overrides;
/**
* The Language object used to override configuration data.
*
* @var \Drupal\Core\Language\Language
*/
protected $language;
/**
* Constructs a configuration overrides event object.
*
* @param array $names
* A list of configuration names.
* @param \Drupal\Core\Language\Language
* (optional) The language for this configuration.
*/
public function __construct(array $names, Language $language = NULL) {
$this->names = $names;
$this->language = $language;
$this->overrides = array();
}
/**
* Gets configuration names.
*
* @return array
* The list of configuration names that can be overridden.
*/
public function getNames() {
return $this->names;
}
/**
* Gets configuration language.
*
* @return \Drupal\Core\Language\Language
* The configuration language object.
*/
public function getLanguage() {
return $this->language;
}
/**
* Get configuration overrides.
*
* @return array.
* The array of configuration overrides.
*/
public function getOverrides() {
return $this->overrides;
}
/**
* Sets a configuration override for the given name.
*
* @param string $name
* The configuration object name to override.
* @param array $values
* The values in the configuration object to override.
*
* @return self
* The ConfigModuleOverridesEvent object.
*/
public function setOverride($name, array $values) {
if (in_array($name, $this->names)) {
if (isset($this->overrides[$name])) {
// Existing overrides take precedence since these will have been added
// by events with a higher priority.
$this->overrides[$name] = NestedArray::mergeDeepArray(array($values, $this->overrides[$name]), TRUE);
}
else {
$this->overrides[$name] = $values;
}
}
return $this;
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Config\Context\ConfigContext.
*/
namespace Drupal\Core\Config\Context;
use Drupal\Core\Config\Config;
use Drupal\Core\Config\ConfigEvent;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Uuid\UuidInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
/**