diff -u b/core/includes/config.inc b/core/includes/config.inc --- b/core/includes/config.inc +++ b/core/includes/config.inc @@ -2,7 +2,6 @@ use Drupal\Core\Config\DatabaseStorage; use Drupal\Core\Config\FileStorage; -use Drupal\Core\Config\DrupalConfig; use Drupal\Core\Config\ConfigException; /** @@ -81,27 +80,30 @@ * The name of the configuration object to retrieve. The name corresponds to * a configuration file. For @code config(book.admin) @endcode, the config * object returned will contain the contents of book.admin configuration file. + * @param $config_class + * (optional) The class name for the configuration object to be returned. + * Defaults to Drupal\Core\Config\DupalConfig. * @param $storage_class - * (optional) A storage driver class name to use for the DrupalConfig object. - * Defaults to DrupalVerifiedStorageSQL. + * (optional) A storage driver class name to use for the configuration class + * object. Defaults to Drupal\Core\Config\DatabaseStorage. * * @return - * An instance of the class specified in the $class parameter. + * An instance of the $config_class class. * - * @todo Replace this with an appropriate factory / ability to inject in - * alternate storage engines.. + * @todo Allow to inject alternate storage engines. */ -function config($name, $storage_class = NULL) { - global $conf; - +function config($name, $config_class = NULL, $storage_class = NULL) { + if (!isset($config_class)) { + $config_class = 'Drupal\Core\Config\DrupalConfig'; + } if (!isset($storage_class)) { - $storage_class = (isset($conf['config_default_storage']) ? $conf['config_default_storage'] : 'Drupal\Core\Config\DatabaseStorage'); + $storage_class = 'Drupal\Core\Config\DatabaseStorage'; } - return new DrupalConfig(new $storage_class($name)); + return new $config_class(new $storage_class($name)); } /** - * Reload config from disk and write new settings to the active store. + * Synchronizes configuration from FileStorage to DatabaseStorage. */ function config_sync() { $config_changes = config_get_changes_from_disk(); @@ -109,45 +111,37 @@ return; } - $lock_attempts = 0; - $lock_acquired = FALSE; - do { - if (!$lock_acquired = lock_acquire(__FUNCTION__, 5)) { - lock_wait(__FUNCTION__, 5); - continue; - } - - try { - config_sync_invoke_sync_hooks($config_changes); - config_sync_save_changes($config_changes); - // Flush all caches and reset static variables after a successful import. - drupal_flush_all_caches(); - } - catch (ConfigException $e) { - watchdog_exception('config_sync', $e); - config_sync_invoke_sync_error_hooks($config_changes); - lock_release(__FUNCTION__); - return; - } - } while ($lock_acquired === FALSE && ++$lock_attempts < 5); - - if ($lock_acquired) { - lock_release(__FUNCTION__); - return TRUE; - } - else { - watchdog('config_sync', 'Failed to get lock while trying to sync config.'); + $lock_name = __FUNCTION__; + if (!lock_acquire($lock_name)) { + // Another request is synchronizing configuration. Wait for it to complete, + // then re-check for any remaining differences. + lock_wait($lock_name); + return config_sync(); + } + + try { + config_sync_invoke_sync_hooks($config_changes); + config_sync_save_changes($config_changes); + // Flush all caches and reset static variables after a successful import. + drupal_flush_all_caches(); + } + catch (ConfigException $e) { + watchdog_exception('config_sync', $e); + config_sync_invoke_sync_error_hooks($config_changes); + lock_release($lock_name); return FALSE; } + lock_release($lock_name); + return TRUE; } /** * Writes an array of config file changes to the active store. * - * @param $config_changes + * @param array $config_changes * An array of changes to be written. */ -function config_sync_save_changes($config_changes) { +function config_sync_save_changes(array $config_changes) { foreach (array('new', 'changed', 'deleted') as $type) { foreach ($config_changes[$type] as $name) { if ($type == 'deleted') { @@ -156,7 +150,7 @@ else { // Get the active store object, set the new data from file, then save. $target_config = config($name); - $source_config = config($name, 'Drupal\Core\Config\FileStorage'); + $source_config = config($name, NULL, 'Drupal\Core\Config\FileStorage'); $target_config->setData($source_config->get()); $target_config->save(); } @@ -167,21 +161,22 @@ /** * Invokes hook_config_sync_validate() and hook_config_sync() implementations. * - * @param $config_changes + * @param array $config_changes * An array of changes to be loaded. */ -function config_sync_invoke_sync_hooks($config_changes) { - // Keep a copy of the changes so that a module can't modify the values by +function config_sync_invoke_sync_hooks(array $config_changes) { + // Keep a copy of the changes so that modules cannot modify the values by // taking the array by reference. $config_changes_copy = $config_changes; // @todo Lock writes to ALL config storages to prevent other/unintended config // changes from happening during the import. - $target_storage = config(NULL, 'Drupal\Core\Config\DatabaseStorage'); - $source_storage = config(NULL, 'Drupal\Core\Config\FileStorage'); + $target_storage = config(NULL, NULL, 'Drupal\Core\Config\DatabaseStorage'); + $source_storage = config(NULL, NULL, 'Drupal\Core\Config\FileStorage'); - foreach (config_sync_sort_dependencies(module_implements('config_sync_validate')) as $module) { + // Allow all modules to deny configuration changes. + foreach (module_implements('config_sync_validate') as $module) { $config_changes = $config_changes_copy; $function = $module . '_config_sync_validate'; $function($config_changes, $target_storage, $source_storage); @@ -189,8 +184,12 @@ // We allow modules to signal that they would like to be rerun after all // other modules by returning CONFIG_DEFER_SYNC. Loop until there are no - // modules left that indicate they would like to be rerun, checking that we're - // not stuck rerunning the same list of modules over and over at each cycle. + // modules left that indicate they would like to be re-run, checking that we + // are not stuck re-running the same list of modules infinitely. + // The list of modules is ordered by the reversed chain of module + // dependencies, in order to invoke dependent modules first, and thus decrease + // the possibility for CONFIG_DEFER_SYNC requests and increase the chance of + // being able to execute all changes in a single loop. $modules = config_sync_sort_dependencies(module_implements('config_sync')); do { // Prevent an infinite loop. If the two variables stay the same, then all @@ -207,10 +206,9 @@ } } while ($modules && $modules != $initial_module_list); - // If there are modules left that haven't run their sync hook, then we hit - // an infinite loop. + // If there are modules left, then we hit an infinite loop. if ($modules) { - throw new ConfigException("Dependency loop detected while sync configuration from disk."); + throw new ConfigException('Unmet dependencies detected during synchronization.'); } } @@ -222,15 +220,14 @@ * made such changes, and make sure that the state of configuration in the * active store is correct. * - * @param $config_changes + * @param array $config_changes * An array of changes to be loaded. */ -function config_sync_invoke_sync_error_hooks($config_changes) { - $target_storage = config(NULL, 'Drupal\Core\Config\DatabaseStorage'); - $source_storage = config(NULL, 'Drupal\Core\Config\FileStorage'); +function config_sync_invoke_sync_error_hooks(array $config_changes) { + $target_storage = config(NULL, NULL, 'Drupal\Core\Config\DatabaseStorage'); + $source_storage = config(NULL, NULL, 'Drupal\Core\Config\FileStorage'); - $modules = config_sync_sort_dependencies(module_implements('config_sync_error')); - foreach ($modules as $module) { + foreach (module_implements('config_sync_error') as $module) { $function = $module . '_config_sync_error'; try { $function($config_changes, $target_storage, $source_storage); @@ -243,15 +240,16 @@ } /** - * Sort the given list of modules based on dependency. + * Sorts a given list of modules based on their dependencies. * - * @param $modules + * @param array $modules * A list of modules. - * @return + * + * @return array * The list of modules sorted by dependency. */ -function config_sync_sort_dependencies($modules) { - // Get all module data so we can find find weights and sort. +function config_sync_sort_dependencies(array $modules) { + // Get all module data so we can find weights and sort. $module_data = system_rebuild_module_data(); $sorted_modules = array(); @@ -265,8 +263,9 @@ /** * Returns a list of changes on disk compared to the active store. * - * @return - * The list of files changed on disk compared to the active store. + * @return array|bool + * The list of files changed on disk compared to the active store, or FALSE if + * there are no differences. */ function config_get_changes_from_disk() { $disk_config_names = FileStorage::getNamesWithPrefix(); @@ -278,14 +277,18 @@ ); foreach (array_intersect($disk_config_names, $active_config_names) as $name) { $active_config = config($name); - $file_config = config($name, 'Drupal\Core\Config\FileStorage'); + $file_config = config($name, NULL, 'Drupal\Core\Config\FileStorage'); if ($active_config->get() != $file_config->get()) { $config_changes['changed'][] = $name; } } + + // Do not trigger subsequent synchronization operations if there are no + // changes in either category. if (empty($config_changes['new']) && empty($config_changes['changed']) && empty($config_changes['deleted'])) { return FALSE; } + return $config_changes; } diff -u b/core/modules/config/config.api.php b/core/modules/config/config.api.php --- b/core/modules/config/config.api.php +++ b/core/modules/config/config.api.php @@ -3,33 +3,123 @@ /** * @file - * Hooks provided by the Config module. + * Hooks provided by the Configuration module. */ /** * @defgroup config_hooks Configuration system hooks * @{ - * TODO: overall description of the configuration system. + * @todo Overall description of the configuration system. * @} */ /** - * Reload config changes from disk. + * Validate configuration changes before they are synchronized. + * + * @param array $config_changes + * An associative array whose keys denote the configuration differences + * ('new', 'changed', 'deleted') and whose values are arrays of configuration + * object names. + * @param $target_storage + * A configuration class acting on the target storage to which the new + * configuration will be written. Use $target_storage->load() to load a + * configuration object. + * @param $source_storage + * A configuration class acting on the source storage from which configuration + * differences were read. Use $target_storage->load() to load a configuration + * object. + * + * @throws ConfigException + * In case a configuration change cannot be allowed. */ -function hook_config_sync($config_changes, $target_storage, $source_storage) { - // TODO: working example. +function hook_config_sync_validate($config_changes, $target_storage, $source_storage) { + // Deny changes to our settings. + if (isset($config_changes['changed']['mymodule.locked'])) { + throw new ConfigException('MyModule settings cannot be changed.'); + } } /** - * Validate configuration changes before they are saved to the active store. + * Synchronize configuration changes. + * + * This hook is invoked when configuration is synchronized between storages and + * allows all modules to react to new, deleted, and changed configuration. This + * hook is invoked before the new configuration is written to the target storage. + * Implementations of this hook only react to the differences. The new + * configuration itself is written by the configuration system. + * + * @param array $config_changes + * An associative array whose keys denote the configuration differences + * ('new', 'changed', 'deleted') and whose values are arrays of configuration + * object names. + * @param $target_storage + * A configuration class acting on the target storage to which the new + * configuration will be written. Use $target_storage->load() to load a + * configuration object. + * @param $source_storage + * A configuration class acting on the source storage from which configuration + * differences were read. Use $target_storage->load() to load a configuration + * object. + * + * @return + * Nothing on successful synchronization, or CONFIG_DEFER_SYNC if a change + * cannot be synchronized yet adn depends on another module to execute first. */ -function hook_config_sync_validate($config_changes, $target_storage, $source_storage) { - // TODO: working example. +function hook_config_sync($config_changes, $target_storage, $source_storage) { + foreach ($config_changes['new'] as $name) { + if (strpos($name, 'image.style.') === 0) { + // Load the new configuration data and inform other modules about it. + $style = $source_storage->load($name)->get(); + $style['is_new'] = TRUE; + module_invoke_all('image_style_save', $style); + } + } + foreach ($config_changes['changed'] as $name) { + if (strpos($name, 'image.style.') === 0) { + // Load the new configuration data, inform other modules about the change, + // and perform any further actions that may be required for the change to + // take effect. + $style = $source_storage->load($name)->get(); + $style['is_new'] = FALSE; + module_invoke_all('image_style_save', $style); + // Delete existing derivative images. + image_style_flush($style); + } + } + foreach ($config_changes['deleted'] as $name) { + if (strpos($name, 'image.style.') === 0) { + // The style has been deleted, so read the previous configuration from the + // old storage. + $style = $target_storage->load($name)->get(); + module_invoke_all('image_style_delete', $style); + // Delete existing derivative images. + image_style_flush($style); + } + } } /** * Validate configuration changes before they are saved to the active store. + * + * During synchronization of configuration, modules may make changes that cannot + * be rolled back. This hook allows modules to react to an error that occurs + * after they have made such changes, and make sure that the state of + * configuration is as correct as possible. + * + * @param array $config_changes + * An associative array whose keys denote the configuration differences + * ('new', 'changed', 'deleted') and whose values are arrays of configuration + * object names. + * @param $target_storage + * A configuration class acting on the target storage to which the new + * configuration will be written. Use $target_storage->load() to load a + * configuration object. + * @param $source_storage + * A configuration class acting on the source storage from which configuration + * differences were read. Use $target_storage->load() to load a configuration + * object. */ function hook_config_sync_error($config_changes, $target_storage, $source_storage) { - // TODO: working example. + // @todo Feasability and usage of this hook is still unclear, without having a + // backup of $target_storage at hand. }