diff --git a/core/lib/Drupal/Core/Config/Schema/ConfigSchemaAlterException.php b/core/lib/Drupal/Core/Config/Schema/ConfigSchemaAlterException.php new file mode 100644 index 0000000000000000000000000000000000000000..fa87e06d964384b40affafc469cafa213d63b863 --- /dev/null +++ b/core/lib/Drupal/Core/Config/Schema/ConfigSchemaAlterException.php @@ -0,0 +1,14 @@ +<?php + +/** + * @file + * Contains \Drupal\Core\Config\Schema\ConfigSchemaAlterException. + */ + +namespace Drupal\Core\Config\Schema; + +/** + * Exception for when hook_config_schema_info_alter() adds or removes schema. + */ +class ConfigSchemaAlterException extends \RuntimeException { +} diff --git a/core/lib/Drupal/Core/Config/TypedConfigManager.php b/core/lib/Drupal/Core/Config/TypedConfigManager.php index a78436404a061009dcb56c384af2960c4936f90f..22f43c1db87351a2dab3382bd8ace6d8ad583b5d 100644 --- a/core/lib/Drupal/Core/Config/TypedConfigManager.php +++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php @@ -8,7 +8,9 @@ namespace Drupal\Core\Config; use Drupal\Component\Utility\NestedArray; +use Drupal\Component\Utility\String; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Config\Schema\ConfigSchemaAlterException; use Drupal\Core\Config\Schema\ConfigSchemaDiscovery; use Drupal\Core\Config\Schema\Element; use Drupal\Core\Extension\ModuleHandlerInterface; @@ -296,6 +298,29 @@ public function hasConfigSchema($name) { return is_array($definition) && ($definition['class'] != '\Drupal\Core\Config\Schema\Undefined'); } + /** + * {@inheritdoc} + */ + protected function alterDefinitions(&$definitions) { + $discovered_schema = array_keys($definitions); + parent::alterDefinitions($definitions); + $altered_schema = array_keys($definitions); + if ($discovered_schema != $altered_schema) { + $added_keys = array_diff($altered_schema, $discovered_schema); + $removed_keys = array_diff($discovered_schema, $altered_schema); + if (!empty($added_keys) && !empty($removed_keys)) { + $message = 'Invoking hook_config_schema_info_alter() has added (@added) and removed (@removed) schema definitions'; + } + elseif (!empty($added_keys)) { + $message = 'Invoking hook_config_schema_info_alter() has added (@added) schema definitions'; + } + else { + $message = 'Invoking hook_config_schema_info_alter() has removed (@removed) schema definitions'; + } + throw new ConfigSchemaAlterException(String::format($message, ['@added' => implode(',', $added_keys), '@removed' => implode(',', $removed_keys)])); + } + } + /** * {@inheritdoc} */ diff --git a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php index 95928e4e24eddefce6414482ba7a8750e9b28aa9..5c37aa99d7bd3f4a8316e713dededb8631698d1f 100644 --- a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php +++ b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php @@ -224,9 +224,7 @@ protected function findDefinitions() { foreach ($definitions as $plugin_id => &$definition) { $this->processDefinition($definition, $plugin_id); } - if ($this->alterHook) { - $this->moduleHandler->alter($this->alterHook, $definitions); - } + $this->alterDefinitions($definitions); // If this plugin was provided by a module that does not exist, remove the // plugin definition. foreach ($definitions as $plugin_id => $plugin_definition) { @@ -242,4 +240,16 @@ protected function findDefinitions() { return $definitions; } + /** + * Invokes the hook to alter the definitions if the alter hook is set. + * + * @param $definitions + * The discovered plugin defintions. + */ + protected function alterDefinitions(&$definitions) { + if ($this->alterHook) { + $this->moduleHandler->alter($this->alterHook, $definitions); + } + } + } diff --git a/core/modules/config/src/Tests/ConfigSchemaTest.php b/core/modules/config/src/Tests/ConfigSchemaTest.php index c7a6f55513ae084c361651dffe79902e10931eda..a2a3510e771bc391807e6d28ae4312c86598befd 100644 --- a/core/modules/config/src/Tests/ConfigSchemaTest.php +++ b/core/modules/config/src/Tests/ConfigSchemaTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Config\FileStorage; use Drupal\Core\Config\InstallStorage; +use Drupal\Core\Config\Schema\ConfigSchemaAlterException; use Drupal\Core\TypedData\Type\IntegerInterface; use Drupal\Core\TypedData\Type\StringInterface; use Drupal\simpletest\KernelTestBase; @@ -436,4 +437,54 @@ function testColonsInSchemaTypeDetermination() { $this->assertEqual($definition['type'], 'test_with_parents.plugin_types.*'); } + /** + * Tests hook_config_schema_info_alter(). + */ + public function testConfigSchemaInfoAlter() { + /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config */ + $typed_config = \Drupal::service('config.typed'); + $typed_config->clearCachedDefinitions(); + + // Ensure that keys can not be added or removed by + // hook_config_schema_info_alter(). + \Drupal::state()->set('config_schema_test_exception_remove', TRUE); + $message = 'Expected ConfigSchemaAlterException thrown.'; + try { + $typed_config->getDefinitions(); + $this->fail($message); + } + catch (ConfigSchemaAlterException $e) { + $this->pass($message); + $this->assertEqual($e->getMessage(), 'Invoking hook_config_schema_info_alter() has removed (config_schema_test.hook) schema definitions'); + } + + \Drupal::state()->set('config_schema_test_exception_add', TRUE); + $message = 'Expected ConfigSchemaAlterException thrown.'; + try { + $typed_config->getDefinitions(); + $this->fail($message); + } + catch (ConfigSchemaAlterException $e) { + $this->pass($message); + $this->assertEqual($e->getMessage(), 'Invoking hook_config_schema_info_alter() has added (config_schema_test.hook_added_defintion) and removed (config_schema_test.hook) schema definitions'); + } + + \Drupal::state()->set('config_schema_test_exception_remove', FALSE); + $message = 'Expected ConfigSchemaAlterException thrown.'; + try { + $typed_config->getDefinitions(); + $this->fail($message); + } + catch (ConfigSchemaAlterException $e) { + $this->pass($message); + $this->assertEqual($e->getMessage(), 'Invoking hook_config_schema_info_alter() has added (config_schema_test.hook_added_defintion) schema definitions'); + } + + // Tests that hook_config_schema_info_alter() can add additional metadata to + // existing configuration schema. + \Drupal::state()->set('config_schema_test_exception_add', FALSE); + $definitions = $typed_config->getDefinitions(); + $this->assertEqual($definitions['config_schema_test.hook']['additional_metadata'], 'new schema info'); + } + } diff --git a/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml b/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml index cbc3c6190023dc8bb1afdaf828f93a5d3d1541f9..719865a47d248d244388e00a1b9b32ac81084dbb 100644 --- a/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml +++ b/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml @@ -207,3 +207,5 @@ test_with_parents.plugin_types.*: value: type: string +config_schema_test.hook: + type: string diff --git a/core/modules/config/tests/config_schema_test/config_schema_test.module b/core/modules/config/tests/config_schema_test/config_schema_test.module new file mode 100644 index 0000000000000000000000000000000000000000..3ce1170222e1a2bcc3dd5472c03a2c429ea76570 --- /dev/null +++ b/core/modules/config/tests/config_schema_test/config_schema_test.module @@ -0,0 +1,23 @@ +<?php + +/** + * @file + * Tests configuration schema functionality. + */ + +/** + * Implements hook_config_schema_info_alter(). + */ +function config_schema_test_config_schema_info_alter(&$definitions) { + if (\Drupal::state()->get('config_schema_test_exception_add')) { + $definitions['config_schema_test.hook_added_defintion'] = $definitions['config_schema_test.hook']; + } + if (\Drupal::state()->get('config_schema_test_exception_remove')) { + unset($definitions['config_schema_test.hook']); + } + + // Since code can not be unloaded only alter the definition if it exists. + if (isset($definitions['config_schema_test.hook'])) { + $definitions['config_schema_test.hook']['additional_metadata'] = 'new schema info'; + } +} diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index cfdf5e2b21068f300d473043b96408002680e4a3..eb4eb9bebebf9a6cff3ebf1dd8e771baa0bb6ff4 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -580,8 +580,8 @@ function hook_config_import_steps_alter(&$sync_steps, \Drupal\Core\Config\Config * configuration schema type to change default labels or form element renderers * used for configuration translation. * - * It is strongly advised not to use this hook to add new data types or to - * change the structure of existing ones. Keep in mind that there are tools + * If implementations of this hook add or remove configuration schema a + * ConfigSchemaAlterException will be thrown. Keep in mind that there are tools * that may use the configuration schema for static analysis of configuration * files, like the string extractor for the localization system. Such systems * won't work with dynamically defined configuration schemas. @@ -591,6 +591,9 @@ function hook_config_import_steps_alter(&$sync_steps, \Drupal\Core\Config\Config * @param $definitions * Associative array of configuration type definitions keyed by schema type * names. The elements are themselves array with information about the type. + * + * @see \Drupal\Core\Config\TypedConfigManager + * @see \Drupal\Core\Config\Schema\ConfigSchemaAlterException */ function hook_config_schema_info_alter(&$definitions) { // Enhance the text and date type definitions with classes to generate proper