Commit 73069e05 authored by catch's avatar catch

Issue #2392319 by alexpott, effulgentsia: Config objects (but not config...

Issue #2392319 by alexpott, effulgentsia: Config objects (but not config entities) should by default be immutable
parent 44313c63
......@@ -1520,9 +1520,10 @@ function drupal_tempnam($directory, $prefix) {
* A string containing the path to the temporary directory.
*/
function file_directory_temp() {
$config = \Drupal::config('system.file');
$temporary_directory = $config->get('path.temporary');
$temporary_directory = \Drupal::config('system.file')->get('path.temporary');
if (empty($temporary_directory)) {
// Needs set up.
$config = \Drupal::configFactory()->getEditable('system.file');
$temporary_directory = file_directory_os_temp();
if (empty($temporary_directory)) {
......
......@@ -1647,7 +1647,7 @@ function install_download_additional_translations_operations(&$install_state) {
// If a non-English language was selected, change the default language and
// remove English.
if ($langcode != 'en') {
\Drupal::config('system.site')->set('langcode', $langcode)->save();
\Drupal::configFactory()->getEditable('system.site')->set('langcode', $langcode)->save();
\Drupal::service('language.default')->set($language);
if (empty($install_state['profile_info']['keep_english'])) {
entity_delete_multiple('configurable_language', array('en'));
......
......@@ -618,7 +618,7 @@ function drupal_install_system($install_state) {
// Ensure default language is saved.
if (isset($install_state['parameters']['langcode'])) {
\Drupal::config('system.site')
\Drupal::configFactory()->getEditable('system.site')
->set('langcode', $install_state['parameters']['langcode'])
->save();
}
......
......@@ -211,8 +211,7 @@ function drupal_required_modules() {
* An integer representing the weight of the module.
*/
function module_set_weight($module, $weight) {
// Update the module weight in the config file that contains it.
$extension_config = \Drupal::config('core.extension');
$extension_config = \Drupal::configFactory()->getEditable('core.extension');
if ($extension_config->get("module.$module") !== NULL) {
$extension_config
->set("module.$module", $weight)
......
......@@ -298,8 +298,8 @@ public static function lock() {
* a configuration file. For @code \Drupal::config('book.admin') @endcode, the config
* object returned will contain the contents of book.admin configuration file.
*
* @return \Drupal\Core\Config\Config
* A configuration object.
* @return \Drupal\Core\Config\ImmutableConfig
* An immutable configuration object.
*/
public static function config($name) {
return static::$container->get('config.factory')->get($name);
......
......@@ -79,8 +79,7 @@ public function __construct($name, StorageInterface $storage, EventDispatcherInt
*/
public function initWithData(array $data) {
parent::initWithData($data);
$this->settingsOverrides = array();
$this->moduleOverrides = array();
$this->resetOverriddenData();
return $this;
}
......
......@@ -102,18 +102,44 @@ public function getOverrideState() {
return $this->useOverrides;
}
/**
* {@inheritdoc}
*/
public function getEditable($name) {
$old_state = $this->getOverrideState();
$this->setOverrideState(FALSE);
$config = $this->doGet($name, FALSE);
$this->setOverrideState($old_state);
return $config;
}
/**
* {@inheritdoc}
*/
public function get($name) {
if ($config = $this->loadMultiple(array($name))) {
return $this->doGet($name);
}
/**
* Returns a configuration object for a given name.
*
* @param string $name
* The name of the configuration object to construct.
* @param bool $immutable
* (optional) Create an immutable configuration object. Defaults to TRUE.
*
* @return \Drupal\Core\Config\Config|\Drupal\Core\Config\ImmutableConfig
* A configuration object.
*/
protected function doGet($name, $immutable = TRUE) {
if ($config = $this->doLoadMultiple(array($name), $immutable)) {
return $config[$name];
}
else {
// If the configuration object does not exist in the configuration
// storage, create a new object and add it to the static cache.
$cache_key = $this->getConfigCacheKey($name);
$this->cache[$cache_key] = new Config($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager);
$cache_key = $this->getConfigCacheKey($name, $immutable);
$this->cache[$cache_key] = $this->createConfigObject($name, $immutable);
if ($this->useOverrides) {
// Get and apply any overrides.
......@@ -134,10 +160,25 @@ public function get($name) {
* {@inheritdoc}
*/
public function loadMultiple(array $names) {
return $this->doLoadMultiple($names);
}
/**
* Returns a list of configuration objects for the given names.
*
* @param array $names
* List of names of configuration objects.
* @param bool $immutable
* (optional) Create an immutable configuration objects. Defaults to TRUE.
*
* @return \Drupal\Core\Config\Config[]|\Drupal\Core\Config\ImmutableConfig[]
* List of successfully loaded configuration objects, keyed by name.
*/
protected function doLoadMultiple(array $names, $immutable = TRUE) {
$list = array();
foreach ($names as $key => $name) {
$cache_key = $this->getConfigCacheKey($name);
$cache_key = $this->getConfigCacheKey($name, $immutable);
if (isset($this->cache[$cache_key])) {
$list[$name] = $this->cache[$cache_key];
unset($names[$key]);
......@@ -156,9 +197,9 @@ public function loadMultiple(array $names) {
}
foreach ($storage_data as $name => $data) {
$cache_key = $this->getConfigCacheKey($name);
$cache_key = $this->getConfigCacheKey($name, $immutable);
$this->cache[$cache_key] = new Config($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager);
$this->cache[$cache_key] = $this->createConfigObject($name, $immutable);
$this->cache[$cache_key]->initWithData($data);
if ($this->useOverrides) {
if (isset($module_overrides[$name])) {
......@@ -230,7 +271,7 @@ public function rename($old_name, $new_name) {
// Prime the cache and load the configuration with the correct overrides.
$config = $this->get($new_name);
$this->eventDispatcher->dispatch(ConfigEvents::RENAME, new ConfigRenameEvent($config, $old_name));
return $config;
return $this;
}
/**
......@@ -254,12 +295,14 @@ public function getCacheKeys() {
*
* @param string $name
* The name of the configuration object.
* @param bool $immutable
* Whether or not the object is mutable.
*
* @return string
* The cache key.
*/
protected function getConfigCacheKey($name) {
return $name . ':' . implode(':', $this->getCacheKeys());
protected function getConfigCacheKey($name, $immutable) {
return $name . ':' . implode(':', $this->getCacheKeys()) . ':' . ($immutable ? static::IMMUTABLE: static::MUTABLE);
}
/**
......@@ -294,7 +337,7 @@ public function listAll($prefix = '') {
}
/**
* Removes stale static cache entries when configuration is saved.
* Updates stale static cache entries when configuration is saved.
*
* @param ConfigCrudEvent $event
* The configuration event.
......@@ -307,7 +350,9 @@ public function onConfigSave(ConfigCrudEvent $event) {
foreach ($this->getConfigCacheKeys($saved_config->getName()) as $cache_key) {
$cached_config = $this->cache[$cache_key];
if ($cached_config !== $saved_config) {
$this->cache[$cache_key]->setData($saved_config->getRawData());
// We can not just update the data since other things about the object
// might have changed. For example, whether or not it is new.
$this->cache[$cache_key]->initWithData($saved_config->getRawData());
}
}
}
......@@ -341,4 +386,22 @@ public function addOverride(ConfigFactoryOverrideInterface $config_factory_overr
$this->configFactoryOverrides[] = $config_factory_override;
}
/**
* Creates a configuration object.
*
* @param string $name
* Configuration object name.
* @param bool $immutable
* Determines whether a mutable or immutable config object is returned.
*
* @return \Drupal\Core\Config\Config|\Drupal\Core\Config\ImmutableConfig
* The configuration object.
*/
protected function createConfigObject($name, $immutable) {
if ($immutable) {
return new ImmutableConfig($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager);
}
return new Config($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager);
}
}
......@@ -14,6 +14,16 @@
*/
interface ConfigFactoryInterface {
/**
* Constant used in static cache keys for mutable config objects.
*/
const MUTABLE = 'mutable';
/**
* Constant used in static cache keys for immutable config objects.
*/
const IMMUTABLE = 'immutable';
/**
* Sets the override state.
*
......@@ -33,26 +43,42 @@ public function setOverrideState($state);
public function getOverrideState();
/**
* Returns a configuration object for a given name.
* Returns an immutable configuration object for a given name.
*
* @param string $name
* The name of the configuration object to construct.
*
* @return \Drupal\Core\Config\Config
* @return \Drupal\Core\Config\ImmutableConfig
* A configuration object.
*/
public function get($name);
/**
* Returns an mutable configuration object for a given name.
*
* Should not be used for config that will have runtime effects. Therefore it
* is always loaded override free.
*
* @param string $name
* The name of the configuration object to construct.
*
* @return \Drupal\Core\Config\Config
* A configuration object.
*/
public function getEditable($name);
/**
* Returns a list of configuration objects for the given names.
*
* This will pre-load all requested configuration objects does not create
* new configuration objects.
* new configuration objects. This method always return immutable objects.
* ConfigFactoryInterface::getEditable() should be used to retrieve mutable
* configuration objects, one by one.
*
* @param array $names
* List of names of configuration objects.
*
* @return \Drupal\Core\Config\Config[]
* @return \Drupal\Core\Config\ImmutableConfig[]
* List of successfully loaded configuration objects, keyed by name.
*/
public function loadMultiple(array $names);
......@@ -76,8 +102,7 @@ public function reset($name = NULL);
* @param string $new_name
* The new name of the configuration object.
*
* @return \Drupal\Core\Config\Config
* The renamed config object.
* @return $this
*/
public function rename($old_name, $new_name);
......
......@@ -224,7 +224,7 @@ public function uninstall($type, $name) {
$config_names = $this->configFactory->listAll($name . '.');
foreach ($config_names as $config_name) {
$this->configFactory->get($config_name)->delete();
$this->configFactory->getEditable($config_name)->delete();
}
// Remove any matching configuration from collections.
......
......@@ -200,8 +200,7 @@ protected function doCreate(array $values) {
*/
protected function doDelete($entities) {
foreach ($entities as $entity) {
$config = $this->configFactory->get($this->getPrefix() . $entity->id());
$config->delete();
$this->configFactory->getEditable($this->getPrefix() . $entity->id())->delete();
}
}
......@@ -238,16 +237,15 @@ public function save(EntityInterface $entity) {
protected function doSave($id, EntityInterface $entity) {
$is_new = $entity->isNew();
$prefix = $this->getPrefix();
$config_name = $prefix . $entity->id();
if ($id !== $entity->id()) {
// Renaming a config object needs to cater for:
// - Storage needs to access the original object.
// - The object needs to be renamed/copied in ConfigFactory and reloaded.
// - All instances of the object need to be renamed.
$config = $this->configFactory->rename($prefix . $id, $prefix . $entity->id());
}
else {
$config = $this->configFactory->get($prefix . $id);
$this->configFactory->rename($prefix . $id, $config_name);
}
$config = $this->configFactory->getEditable($config_name);
// Retrieve the desired properties and set them in config.
$config->setData($this->mapToStorageRecord($entity));
......
<?php
/**
* @file
* Contains \Drupal\Core\Config\ImmutableConfig.
*/
namespace Drupal\Core\Config;
use Drupal\Component\Utility\String;
/**
* Defines the immutable configuration object.
*
* Encapsulates all capabilities needed for runtime configuration handling
* except being able to change the configuration.
*
* If you need to be able to change configuration use
* \Drupal\Core\Form\ConfigFormBaseTrait or
* \Drupal\Core\Config\ConfigFactoryInterface::getEditable().
*
* @see \Drupal\Core\Form\ConfigFormBaseTrait
* @see \Drupal\Core\Config\ConfigFactoryInterface::getEditable()
* @see \Drupal\Core\Config\ConfigFactoryInterface::get()
*
* @ingroup config_api
*/
class ImmutableConfig extends Config {
/**
* {@inheritdoc}
*/
public function set($key, $value) {
throw new ImmutableConfigException(String::format('Can not set values on immutable configuration !name:!key. Use \Drupal\Core\Config\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object', ['!name' => $this->getName(), '!key' => $key]));
}
/**
* {@inheritdoc}
*/
public function clear($key) {
throw new ImmutableConfigException(String::format('Can not clear !key key in immutable configuration !name. Use \Drupal\Core\Config\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object', ['!name' => $this->getName(), '!key' => $key]));
}
/**
* {@inheritdoc}
*/
public function save() {
throw new ImmutableConfigException(String::format('Can not save immutable configuration !name. Use \Drupal\Core\Config\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object', ['!name' => $this->getName()]));
}
/**
* Deletes the configuration object.
*
* @return \Drupal\Core\Config\Config
* The configuration object.
*/
public function delete() {
throw new ImmutableConfigException(String::format('Can not delete immutable configuration !name. Use \Drupal\Core\Config\ConfigFactoryInterface::getEditable() to retrieve a mutable configuration object', ['!name' => $this->getName()]));
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Config\ImmutableConfigException.
*/
namespace Drupal\Core\Config;
/**
* Exception throw when an immutable config object is altered.
*
* @see \Drupal\Core\Config\ImmutableConfig
*/
class ImmutableConfigException extends \LogicException {
}
......@@ -79,7 +79,7 @@ public function addUninstallValidator(ModuleUninstallValidatorInterface $uninsta
* {@inheritdoc}
*/
public function install(array $module_list, $enable_dependencies = TRUE) {
$extension_config = \Drupal::config('core.extension');
$extension_config = \Drupal::configFactory()->getEditable('core.extension');
if ($enable_dependencies) {
// Get all module data so we can find dependencies and sort.
$module_data = system_rebuild_module_data();
......@@ -300,8 +300,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
return FALSE;
}
// Only process currently installed modules.
$extension_config = \Drupal::config('core.extension');
$extension_config = \Drupal::configFactory()->getEditable('core.extension');
$installed_modules = $extension_config->get('module') ?: array();
if (!$module_list = array_intersect_key($module_list, $installed_modules)) {
// Nothing to do. All modules already uninstalled.
......@@ -387,8 +386,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
drupal_uninstall_schema($module);
// Remove the module's entry from the config.
$extension_config = \Drupal::config('core.extension');
$extension_config->clear("module.$module")->save();
\Drupal::configFactory()->getEditable('core.extension')->clear("module.$module")->save();
// Update the module handler to remove the module.
// The current ModuleHandler instance is obsolete with the kernel rebuild
......
......@@ -179,7 +179,7 @@ public function setDefault($name) {
if (!isset($list[$name])) {
throw new \InvalidArgumentException("$name theme is not installed.");
}
$this->configFactory->get('system.theme')
$this->configFactory->getEditable('system.theme')
->set('default', $name)
->save();
return $this;
......@@ -189,7 +189,7 @@ public function setDefault($name) {
* {@inheritdoc}
*/
public function install(array $theme_list, $install_dependencies = TRUE) {
$extension_config = $this->configFactory->get('core.extension');
$extension_config = $this->configFactory->getEditable('core.extension');
$theme_data = $this->rebuildThemeData();
......@@ -313,8 +313,8 @@ public function install(array $theme_list, $install_dependencies = TRUE) {
* {@inheritdoc}
*/
public function uninstall(array $theme_list) {
$extension_config = $this->configFactory->get('core.extension');
$theme_config = $this->configFactory->get('system.theme');
$extension_config = $this->configFactory->getEditable('core.extension');
$theme_config = $this->configFactory->getEditable('system.theme');
$list = $this->listInfo();
foreach ($theme_list as $key) {
if (!isset($list[$key])) {
......
......@@ -15,6 +15,7 @@
* Base class for implementing system configuration forms.
*/
abstract class ConfigFormBase extends FormBase {
use ConfigFormBaseTrait;
/**
* Constructs a \Drupal\system\ConfigFormBase object.
......@@ -59,20 +60,4 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
drupal_set_message($this->t('The configuration options have been saved.'));
}
/**
* {@inheritdoc}
*
* Overrides \Drupal\Core\Form\FormBase::config() so that configuration is
* returned override free. This ensures that overrides do not pollute saved
* configuration.
*/
protected function config($name) {
$config_factory = $this->configFactory();
$old_state = $config_factory->getOverrideState();
$config_factory->setOverrideState(FALSE);
$config = $config_factory->get($name);
$config_factory->setOverrideState($old_state);
return $config;
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Form\ConfigFormBaseTrait.
*/
namespace Drupal\Core\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
/**
* Provides access to configuration for forms.
*
* This trait provides a config() method that returns override free and mutable
* config objects if the configuration name is in the array returned by the
* getEditableConfigNames() implementation.
*
* Forms that present configuration to the user have to take care not to save
* configuration overrides to the stored configuration since overrides are often
* environment specific. Default values of form elements should be obtained from
* override free configuration objects. However, if a form reacts to
* configuration in any way, for example sends an email to the system.site:mail
* address, then it is important that the value comes from a configuration
* object with overrides. Therefore, override free and editable configuration
* objects are limited to those listed by the getEditableConfigNames() method.
*/
trait ConfigFormBaseTrait {
/**
* Retrieves a configuration object.
*
* Objects that use the trait need to implement the
* \Drupal\Core\Form\ConfigFormBaseTrait::getEditableConfigNames() to declare
* which configuration objects this method returns override free and mutable.
* This ensures that overrides do not pollute saved configuration.
*
* @param string $name
* The name of the configuration object to retrieve. The name corresponds to
* a configuration file. For @code \Drupal::config('book.admin') @endcode,
* the config object returned will contain the contents of book.admin
* configuration file.
*
* @return \Drupal\Core\Config\Config|\Drupal\Core\Config\ImmutableConfig
* A configuration object.
*/
protected function config($name) {
/** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
if (method_exists($this, 'configFactory')) {
$config_factory = $this->configFactory();
}
elseif (property_exists($this, 'configFactory')) {
$config_factory = $this->configFactory;
}
if (!isset($config_factory) || !($config_factory instanceof ConfigFactoryInterface)) {
throw new \LogicException('No config factory available for ConfigFormBaseTrait');
}
if (in_array($name, $this->getEditableConfigNames())) {
// Get a mutable object from the factory.
$config = $config_factory->getEditable($name);
}
else {
$config = $config_factory->get($name);
}
return $config;
}
/**
* Gets the configuration names that will be editable.
*
* @return array
* An array of configuration object names that are editable if called in
* conjunction with the trait's config() method.
*/
abstract protected function getEditableConfigNames();
}
......@@ -87,7 +87,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
* the config object returned will contain the contents of book.admin
* configuration file.
*
* @return \Drupal\Core\Config\Config
* @return \Drupal\Core\Config\ImmutableConfig
* A configuration object.
*/
protected function config($name) {
......
......@@ -97,6 +97,17 @@ public function getFormId() {
return 'install_configure_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return [
'system.date',
'system.site',
'update.settings',
];
}
/**
* {@inheritdoc}
*/
......
......@@ -17,6 +17,10 @@ class StaticMenuLinkOverrides implements StaticMenuLinkOverridesInterface {
/**
* The config name used to store the overrides.
*
* This configuration can not be overridden by configuration overrides because
* menu links and these overrides are cached in a way that is not override
* aware.
*
* @var string
*/
protected $configName = 'core.menu.static_menu_link_overrides';
......@@ -54,7 +58,8 @@ public function __construct(ConfigFactoryInterface $config_factory) {
*/
protected function getConfig() {
if (empty($this->config)) {
$this->config = $this->configFactory->get($this->configName);
// Get an override free and editable configuration object.
$this->config = $this->configFactory->getEditable($this->configName);
}
return $this->config;
}
......
......@@ -96,6 +96,13 @@ public function getFormId() {
return 'aggregator_admin_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['aggregator.settings'];
}
/**
* {@inheritdoc}
*/
......
......@@ -16,6 +16,7 @@
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Datetime\DateFormatter;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Form\ConfigFormBaseTrait;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\UrlGeneratorTrait;
......@@ -33,7 +34,7 @@
* )
*/
class DefaultProcessor extends AggregatorPluginSettingsBase implements ProcessorInterface, ContainerFactoryPluginInterface {
use ConfigFormBaseTrait;
use UrlGeneratorTrait;
/**
......@@ -107,11 +108,19 @@ public static function create(ContainerInterface $container, array $configuratio
);
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['aggregator.settings'];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$processors = $this->configuration['processors'];
$config = $this->config('aggregator.settings');
$processors = $config-><