diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index 141686f..1c48f4a 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -297,12 +297,14 @@ public static function lock() { * 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. + * @param bool $mutable + * (optional) Create an mutable configuration object. Defaults to TRUE. * * @return \Drupal\Core\Config\Config * A configuration object. */ - public static function config($name) { - return static::$container->get('config.factory')->get($name); + public static function config($name, $mutable = TRUE) { + return static::$container->get('config.factory')->get($name, $mutable); } /** diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php index 970a500..38df2d0 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactory.php +++ b/core/lib/Drupal/Core/Config/ConfigFactory.php @@ -104,12 +104,12 @@ public function getOverrideState() { /** * {@inheritdoc} */ - public function get($name) { - if ($config = $this->loadMultiple(array($name))) { + public function get($name, $mutable = TRUE) { + if ($config = $this->loadMultiple(array($name), $mutable)) { return $config[$name]; } else { - $cache_key = $this->getConfigCacheKey($name); + $cache_key = $this->getConfigCacheKey($name, $mutable); // If the config object has been deleted it will already exist in the // cache but self::loadMultiple does not return such objects. // @todo Explore making ConfigFactory a listener to the config.delete @@ -118,7 +118,7 @@ public function get($name) { // If the configuration object does not exist in the configuration // storage or static cache create a new object and add it to the static // cache. - $this->cache[$cache_key] = new Config($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager); + $this->cache[$cache_key] = $this->createConfigObject($name, $mutable); if ($this->useOverrides) { // Get and apply any overrides. @@ -139,13 +139,13 @@ public function get($name) { /** * {@inheritdoc} */ - public function loadMultiple(array $names) { + public function loadMultiple(array $names, $mutable = TRUE) { $list = array(); foreach ($names as $key => $name) { // @todo: Deleted configuration stays in $this->cache, only return // configuration objects that are not new. - $cache_key = $this->getConfigCacheKey($name); + $cache_key = $this->getConfigCacheKey($name, $mutable); if (isset($this->cache[$cache_key]) && !$this->cache[$cache_key]->isNew()) { $list[$name] = $this->cache[$cache_key]; unset($names[$key]); @@ -164,9 +164,9 @@ public function loadMultiple(array $names) { } foreach ($storage_data as $name => $data) { - $cache_key = $this->getConfigCacheKey($name); + $cache_key = $this->getConfigCacheKey($name, $mutable); - $this->cache[$cache_key] = new Config($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager); + $this->cache[$cache_key] = $this->createConfigObject($name, $mutable); $this->cache[$cache_key]->initWithData($data); if ($this->useOverrides) { if (isset($module_overrides[$name])) { @@ -226,15 +226,15 @@ public function reset($name = NULL) { /** * {@inheritdoc} */ - public function rename($old_name, $new_name) { + public function rename($old_name, $new_name, $mutable = TRUE) { $this->storage->rename($old_name, $new_name); - $old_cache_key = $this->getConfigCacheKey($old_name); - if (isset($this->cache[$old_cache_key])) { + $old_cache_keys = $this->getConfigCacheKeys($old_name); + foreach ($old_cache_keys as $old_cache_key) { unset($this->cache[$old_cache_key]); } // Prime the cache and load the configuration with the correct overrides. - $config = $this->get($new_name); + $config = $this->get($new_name, $mutable); $this->eventDispatcher->dispatch(ConfigEvents::RENAME, new ConfigRenameEvent($config, $old_name)); return $config; } @@ -260,12 +260,14 @@ public function getCacheKeys() { * * @param string $name * The name of the configuration object. + * @param bool $mutable + * 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, $mutable) { + return $name . ':' . implode(':', $this->getCacheKeys()) . ':' . ($mutable ? static::MUTABLE: static::IMMUTABLE); } /** @@ -333,4 +335,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 $mutable + * 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, $mutable) { + if (!$mutable) { + return new ImmutableConfig($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager); + } + return new Config($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager); + + } } diff --git a/core/lib/Drupal/Core/Config/ConfigFactoryInterface.php b/core/lib/Drupal/Core/Config/ConfigFactoryInterface.php index d51e023..751007b 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactoryInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigFactoryInterface.php @@ -15,6 +15,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. * * @param bool $state @@ -37,11 +47,13 @@ public function getOverrideState(); * * @param string $name * The name of the configuration object to construct. + * @param bool $mutable + * (optional) Create an mutable configuration object. Defaults to TRUE. * - * @return \Drupal\Core\Config\Config + * @return \Drupal\Core\Config\Config|\Drupal\Core\Config\ImmutableConfig * A configuration object. */ - public function get($name); + public function get($name, $mutable = TRUE); /** * Returns a list of configuration objects for the given names. @@ -51,11 +63,13 @@ public function get($name); * * @param array $names * List of names of configuration objects. + * @param bool $mutable + * (optional) Create an mutable configuration object. Defaults to TRUE. * - * @return \Drupal\Core\Config\Config[] + * @return \Drupal\Core\Config\Config[]|\Drupal\Core\Config\ImmutableConfig[] * List of successfully loaded configuration objects, keyed by name. */ - public function loadMultiple(array $names); + public function loadMultiple(array $names, $mutable = TRUE); /** * Resets and re-initializes configuration objects. Internal use only. @@ -75,11 +89,13 @@ public function reset($name = NULL); * The old name of the configuration object. * @param string $new_name * The new name of the configuration object. + * @param bool $mutable + * (optional) Create an mutable configuration object. Defaults to TRUE. * * @return \Drupal\Core\Config\Config * The renamed config object. */ - public function rename($old_name, $new_name); + public function rename($old_name, $new_name, $mutable = TRUE); /** * The cache keys associated with the state of the config factory. diff --git a/core/lib/Drupal/Core/Config/ImmutableConfig.php b/core/lib/Drupal/Core/Config/ImmutableConfig.php new file mode 100644 index 0000000..90302cb --- /dev/null +++ b/core/lib/Drupal/Core/Config/ImmutableConfig.php @@ -0,0 +1,42 @@ + $this->getName(), '@key' => $key))); + } + + /** + * {@inheritdoc} + */ + public function clear($key) { + throw new ImmutableConfigException(String::format('@config_name:@key cannot be cleared on an immutable object', array('@config_name' => $this->getName(), '@key' => $key))); + } + + /** + * {@inheritdoc} + */ + public function save() { + throw new ImmutableConfigException(String::format('@config_name cannot be saved, it is an immutable object', array('@config_name' => $this->getName()))); + } + + /** + * {@inheritdoc} + */ + public function delete() { + throw new ImmutableConfigException(String::format('@config_name cannot be deleted, it is an immutable object', array('@config_name' => $this->getName()))); + } + +} diff --git a/core/lib/Drupal/Core/Config/ImmutableConfigException.php b/core/lib/Drupal/Core/Config/ImmutableConfigException.php new file mode 100644 index 0000000..fca05a3 --- /dev/null +++ b/core/lib/Drupal/Core/Config/ImmutableConfigException.php @@ -0,0 +1,12 @@ +configFactory()->get($name); + return $this->configFactory()->get($name, FALSE); } /** diff --git a/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php b/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php index 750e967..ba55764 100644 --- a/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php +++ b/core/lib/Drupal/Core/Installer/Form/SiteConfigureForm.php @@ -8,7 +8,7 @@ namespace Drupal\Core\Installer\Form; use Drupal\Core\Extension\ModuleInstallerInterface; -use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Locale\CountryManagerInterface; use Drupal\Core\State\StateInterface; @@ -18,7 +18,7 @@ /** * Provides the site configuration form. */ -class SiteConfigureForm extends FormBase { +class SiteConfigureForm extends ConfigFormBase { /** * The user storage. diff --git a/core/lib/Drupal/Core/Menu/StaticMenuLinkOverrides.php b/core/lib/Drupal/Core/Menu/StaticMenuLinkOverrides.php index ebde906..4c4370b 100644 --- a/core/lib/Drupal/Core/Menu/StaticMenuLinkOverrides.php +++ b/core/lib/Drupal/Core/Menu/StaticMenuLinkOverrides.php @@ -80,7 +80,9 @@ public function loadOverride($id) { * {@inheritdoc} */ public function deleteMultipleOverrides(array $ids) { - $all_overrides = $this->getConfig()->get('definitions'); + // Get the original configuration without overrides to ensure that global + // overrides are saved to configuration. + $all_overrides = $this->getConfig()->getOriginal('definitions', FALSE); $save = FALSE; foreach ($ids as $id) { $id = static::encodeId($id); @@ -134,11 +136,15 @@ public function saveOverride($id, array $definition) { // Filter the overrides to only those that are expected. $definition = array_intersect_key($definition, $expected); if ($definition) { - $id = static::encodeId($id); - $all_overrides = $this->getConfig()->get('definitions'); - // Combine with any existing data. - $all_overrides[$id] = $definition + $this->loadOverride($id); - $this->getConfig()->set('definitions', $all_overrides)->save(); + $config_key = 'definitions.' . static::encodeId($id); + // Get the original configuration without overrides to ensure that global + // overrides are saved to configuration. + $existing_override = $this->getConfig()->getOriginal($config_key, FALSE); + if ($existing_override) { + // Combine with any existing data. + $definition += $existing_override; + } + $this->getConfig()->set($config_key, $definition)->save(); } return array_keys($definition); } diff --git a/core/modules/config/src/Form/ConfigSingleImportForm.php b/core/modules/config/src/Form/ConfigSingleImportForm.php index 58156b0..d67fc12 100644 --- a/core/modules/config/src/Form/ConfigSingleImportForm.php +++ b/core/modules/config/src/Form/ConfigSingleImportForm.php @@ -265,4 +265,20 @@ public function submitForm(array &$form, FormStateInterface $form_state) { } } + /** + * {@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; + } + } diff --git a/core/modules/contact/src/ContactFormEditForm.php b/core/modules/contact/src/ContactFormEditForm.php index 123ba38..ffda57f 100644 --- a/core/modules/contact/src/ContactFormEditForm.php +++ b/core/modules/contact/src/ContactFormEditForm.php @@ -7,9 +7,11 @@ namespace Drupal\contact; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Form\FormStateInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Base form for contact form edit forms. @@ -17,13 +19,39 @@ class ContactFormEditForm extends EntityForm { /** + * The config factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory. + */ + public function __construct(ConfigFactoryInterface $config_factory) { + $this->configFactory = $config_factory; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('config.factory') + ); + } + + /** * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { $form = parent::form($form, $form_state); $contact_form = $this->entity; - $default_form = $this->config('contact.settings')->get('default_form'); + // Get value without any overrides so we don't copy overrides into active + // configuration. + $default_form = $this->configFactory->get('contact.settings')->getOriginal('default_form', FALSE); $form['label'] = array( '#type' => 'textfield', @@ -94,7 +122,7 @@ public function validate(array $form, FormStateInterface $form_state) { public function save(array $form, FormStateInterface $form_state) { $contact_form = $this->entity; $status = $contact_form->save(); - $contact_settings = $this->config('contact.settings'); + $contact_settings = $this->configFactory->get('contact.settings'); $edit_link = $this->entity->link($this->t('Edit')); if ($status == SAVED_UPDATED) { diff --git a/core/modules/system/src/Form/ThemeAdminForm.php b/core/modules/system/src/Form/ThemeAdminForm.php index 2cee8c5..93cace8 100644 --- a/core/modules/system/src/Form/ThemeAdminForm.php +++ b/core/modules/system/src/Form/ThemeAdminForm.php @@ -6,13 +6,13 @@ namespace Drupal\system\Form; -use Drupal\Core\Form\FormBase; +use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; /** * Form to select the administration theme. */ -class ThemeAdminForm extends FormBase { +class ThemeAdminForm extends ConfigFormBase { /** * {@inheritdoc} @@ -50,7 +50,7 @@ public function buildForm(array $form, FormStateInterface $form_state, array $th * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - drupal_set_message($this->t('The configuration options have been saved.')); + parent::submitForm($form, $form_state); $this->config('system.theme')->set('admin', $form_state->getValue('admin_theme'))->save(); } diff --git a/core/modules/system/tests/modules/form_test/src/FormTestArgumentsObject.php b/core/modules/system/tests/modules/form_test/src/FormTestArgumentsObject.php index da7b4d5..3f56422 100644 --- a/core/modules/system/tests/modules/form_test/src/FormTestArgumentsObject.php +++ b/core/modules/system/tests/modules/form_test/src/FormTestArgumentsObject.php @@ -55,7 +55,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { */ public function submitForm(array &$form, FormStateInterface $form_state) { drupal_set_message($this->t('The FormTestArgumentsObject::submitForm() method was used for this form.')); - $this->config('form_test.object') + $this->configFactory()->get('form_test.object') ->set('bananas', $form_state->getValue('bananas')) ->save(); } diff --git a/core/modules/system/tests/modules/form_test/src/FormTestControllerObject.php b/core/modules/system/tests/modules/form_test/src/FormTestControllerObject.php index a3e5064..ae58495 100644 --- a/core/modules/system/tests/modules/form_test/src/FormTestControllerObject.php +++ b/core/modules/system/tests/modules/form_test/src/FormTestControllerObject.php @@ -65,7 +65,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { */ public function submitForm(array &$form, FormStateInterface $form_state) { drupal_set_message($this->t('The FormTestControllerObject::submitForm() method was used for this form.')); - $this->config('form_test.object') + $this->configFactory()->get('form_test.object') ->set('bananas', $form_state->getValue('bananas')) ->save(); } diff --git a/core/modules/system/tests/modules/form_test/src/FormTestObject.php b/core/modules/system/tests/modules/form_test/src/FormTestObject.php index 4669bfe..0274a91 100644 --- a/core/modules/system/tests/modules/form_test/src/FormTestObject.php +++ b/core/modules/system/tests/modules/form_test/src/FormTestObject.php @@ -56,7 +56,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { */ public function submitForm(array &$form, FormStateInterface $form_state) { drupal_set_message($this->t('The FormTestObject::submitForm() method was used for this form.')); - $this->config('form_test.object') + $this->configFactory()->get('form_test.object') ->set('bananas', $form_state->getValue('bananas')) ->save(); } diff --git a/core/modules/system/tests/modules/form_test/src/FormTestServiceObject.php b/core/modules/system/tests/modules/form_test/src/FormTestServiceObject.php index de4e6c0..30587df 100644 --- a/core/modules/system/tests/modules/form_test/src/FormTestServiceObject.php +++ b/core/modules/system/tests/modules/form_test/src/FormTestServiceObject.php @@ -54,7 +54,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) { */ public function submitForm(array &$form, FormStateInterface $form_state) { drupal_set_message($this->t('The FormTestServiceObject::submitForm() method was used for this form.')); - $this->config('form_test.object') + $this->configFactory()->get('form_test.object') ->set('bananas', $form_state->getValue('bananas')) ->save(); } diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php index ddfbd81..2e07572 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php @@ -722,7 +722,7 @@ public function testDelete() { $config_object->expects($this->once()) ->method('delete'); $configs[] = $config_object; - $config_map[] = array("the_config_prefix.$id", $config_object); + $config_map[] = array("the_config_prefix.$id", TRUE, $config_object); } $this->cacheBackend->expects($this->once()) diff --git a/core/tests/Drupal/Tests/Core/Menu/StaticMenuLinkOverridesTest.php b/core/tests/Drupal/Tests/Core/Menu/StaticMenuLinkOverridesTest.php index c436d6f..cb554fe 100644 --- a/core/tests/Drupal/Tests/Core/Menu/StaticMenuLinkOverridesTest.php +++ b/core/tests/Drupal/Tests/Core/Menu/StaticMenuLinkOverridesTest.php @@ -105,41 +105,31 @@ public function testSaveOverride() { $config = $this->getMockBuilder('Drupal\Core\Config\Config') ->disableOriginalConstructor() ->getMock(); + + $definition_test1 = array('parent' => 'test0'); + $definition_test2 = array('parent' => 'test1'); + $config->expects($this->at(0)) - ->method('get') - ->with('definitions') - ->will($this->returnValue(array())); + ->method('getOriginal') + ->with('definitions.test1', FALSE) + ->willReturn(FALSE); $config->expects($this->at(1)) - ->method('get') - ->with('definitions') - ->will($this->returnValue(array())); - - $definition_save_1 = array('definitions' => array('test1' => array('parent' => 'test0'))); - $definitions_save_2 = array( - 'definitions' => array( - 'test1' => array('parent' => 'test0'), - 'test1__la___ma' => array('parent' => 'test1') - ) - ); - $config->expects($this->at(2)) ->method('set') - ->with('definitions', $definition_save_1['definitions']) - ->will($this->returnSelf()); - $config->expects($this->at(3)) + ->with('definitions.test1', $definition_test1) + ->willReturnSelf(); + $config->expects($this->at(2)) ->method('save'); + // Ensure that existing values are merge in and that IDs are massaged to + // work in configuration. + $config->expects($this->at(3)) + ->method('getOriginal') + ->with('definitions.test1__la___ma', FALSE) + ->willReturn(array('weight' => 5)); $config->expects($this->at(4)) - ->method('get') - ->with('definitions') - ->will($this->returnValue($definition_save_1['definitions'])); - $config->expects($this->at(5)) - ->method('get') - ->with('definitions') - ->will($this->returnValue($definition_save_1['definitions'])); - $config->expects($this->at(6)) ->method('set') - ->with('definitions', $definitions_save_2['definitions']) - ->will($this->returnSelf()); - $config->expects($this->at(7)) + ->with('definitions.test1__la___ma', $definition_test2 + array('weight' => 5)) + ->willReturnSelf(); + $config->expects($this->at(5)) ->method('save'); $config_factory = $this->getMock('Drupal\Core\Config\ConfigFactoryInterface'); @@ -149,8 +139,11 @@ public function testSaveOverride() { $static_override = new StaticMenuLinkOverrides($config_factory); - $static_override->saveOverride('test1', array('parent' => 'test0')); - $static_override->saveOverride('test1.la__ma', array('parent' => 'test1')); + $properties = $static_override->saveOverride('test1', $definition_test1); + $this->assertEquals(['parent'], $properties); + + $properties = $static_override->saveOverride('test1.la__ma', $definition_test2); + $this->assertEquals(['parent', 'weight'], $properties); } /** @@ -170,8 +163,8 @@ public function testDeleteOverrides($ids, array $old_definitions, array $new_def ->disableOriginalConstructor() ->getMock(); $config->expects($this->at(0)) - ->method('get') - ->with('definitions') + ->method('getOriginal') + ->with('definitions', FALSE) ->will($this->returnValue($old_definitions)); // Only save if the definitions changes. diff --git a/core/tests/Drupal/Tests/UnitTestCase.php b/core/tests/Drupal/Tests/UnitTestCase.php index 43748e8..430b468 100644 --- a/core/tests/Drupal/Tests/UnitTestCase.php +++ b/core/tests/Drupal/Tests/UnitTestCase.php @@ -119,7 +119,7 @@ public function getConfigFactoryStub(array $configs = array()) { ->method('get') ->will($this->returnValueMap($map)); - $config_map[] = array($config_name, $config_object); + $config_map[] = array($config_name, TRUE, $config_object); } // Construct a config factory with the array of configuration object stubs // as its return map.