Issue #1890784 by alexpott, YesCT, heyrocker, tim.plunkett, EllaTheHarpy, beejeebus: Refactor configuration import and sync functions.
parent
3a8e8c6082
commit
4eaa775b10
|
@ -356,6 +356,15 @@ services:
|
|||
class: Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
config_import_subscriber:
|
||||
class: Drupal\Core\EventSubscriber\ConfigImportSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
config_snapshot_subscriber:
|
||||
class: Drupal\Core\EventSubscriber\ConfigSnapshotSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
arguments: ['@config.storage', '@config.storage.snapshot']
|
||||
language_request_subscriber:
|
||||
class: Drupal\Core\EventSubscriber\LanguageRequestSubscriber
|
||||
tags:
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Config\ConfigException;
|
||||
use Drupal\Core\Config\ConfigInstaller;
|
||||
use Drupal\Core\Config\FileStorage;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\Config\StorageComparer;
|
||||
use Symfony\Component\Yaml\Dumper;
|
||||
|
||||
/**
|
||||
|
@ -11,11 +13,6 @@ use Symfony\Component\Yaml\Dumper;
|
|||
* This is the API for configuration storage.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Config import lock name used to prevent concurrent synchronizations.
|
||||
*/
|
||||
const CONFIG_IMPORT_LOCK = 'config_import';
|
||||
|
||||
/**
|
||||
* Installs the default configuration of a given extension.
|
||||
*
|
||||
|
@ -25,10 +22,6 @@ const CONFIG_IMPORT_LOCK = 'config_import';
|
|||
* The name of the module or theme to install default configuration for.
|
||||
*/
|
||||
function config_install_default_config($type, $name) {
|
||||
// Use the override free context for config importing so that any overrides do
|
||||
// not change the data on import.
|
||||
config_context_enter('config.context.free');
|
||||
|
||||
// If this module defines any ConfigEntity types then create an empty
|
||||
// manifest file for each of them.
|
||||
foreach (config_get_module_config_entities($name) as $entity_info) {
|
||||
|
@ -38,22 +31,19 @@ function config_install_default_config($type, $name) {
|
|||
$config_dir = drupal_get_path($type, $name) . '/config';
|
||||
if (is_dir($config_dir)) {
|
||||
$source_storage = new FileStorage($config_dir);
|
||||
$target_storage = drupal_container()->get('config.storage');
|
||||
|
||||
// Ignore manifest files.
|
||||
$config_changes = config_sync_get_changes($source_storage, $target_storage, FALSE);
|
||||
if (empty($config_changes['create'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not overwrite or delete pre-existing configuration.
|
||||
$config_changes['change'] = array();
|
||||
$config_changes['delete'] = array();
|
||||
$remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
|
||||
config_sync_changes($remaining_changes, $source_storage, $target_storage);
|
||||
$storage_comparer = new StorageComparer($source_storage, Drupal::service('config.storage'));
|
||||
// Only import new config. Changed config is from previous enables and
|
||||
// should not be overwritten.
|
||||
$storage_comparer->addChangelistCreate();
|
||||
$installer = new ConfigInstaller(
|
||||
$storage_comparer,
|
||||
Drupal::service('event_dispatcher'),
|
||||
Drupal::service('config.factory'),
|
||||
Drupal::entityManager(),
|
||||
Drupal::lock()
|
||||
);
|
||||
$installer->import();
|
||||
}
|
||||
// Exit the override free context.
|
||||
config_context_leave();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,227 +144,6 @@ function config_context_leave() {
|
|||
->leaveContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of differences between configuration storages.
|
||||
*
|
||||
* @param Drupal\Core\Config\StorageInterface $source_storage
|
||||
* The storage to synchronize configuration from.
|
||||
* @param Drupal\Core\Config\StorageInterface $target_storage
|
||||
* The storage to synchronize configuration to.
|
||||
* @param bool $use_manifest
|
||||
* (optional) Whether to determine changes based on manifest files. Defaults
|
||||
* to TRUE.
|
||||
*
|
||||
* @return array|bool
|
||||
* An associative array containing the differences between source and target
|
||||
* storage, or FALSE if there are no differences.
|
||||
*/
|
||||
function config_sync_get_changes(StorageInterface $source_storage, StorageInterface $target_storage, $use_manifest = TRUE) {
|
||||
$config_changes = array(
|
||||
'create' => array(),
|
||||
'change' => array(),
|
||||
'delete' => array(),
|
||||
);
|
||||
$all_source_names = $source_storage->listAll();
|
||||
$all_target_names = $target_storage->listAll();
|
||||
|
||||
// Config entities maintain 'manifest' files that list the objects they
|
||||
// are currently handling. Each file is a simple indexed array of config
|
||||
// object names. In order to generate a list of objects that have been
|
||||
// created or deleted we need to open these files in both the source and
|
||||
// target storage, generate an array of the objects, and compare them.
|
||||
if ($use_manifest) {
|
||||
$source_config_data = array();
|
||||
$target_config_data = array();
|
||||
foreach ($source_storage->listAll('manifest') as $name) {
|
||||
if ($source_manifest_data = $source_storage->read($name)) {
|
||||
$source_config_data = array_merge($source_config_data, $source_manifest_data);
|
||||
}
|
||||
|
||||
if ($target_manifest_data = $target_storage->read($name)) {
|
||||
$target_config_data = array_merge($target_config_data, $target_manifest_data);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_diff_key($target_config_data, $source_config_data) as $name => $value) {
|
||||
$config_changes['delete'][] = $value['name'];
|
||||
}
|
||||
|
||||
foreach (array_diff_key($source_config_data, $target_config_data) as $name => $value) {
|
||||
$config_changes['create'][] = $value['name'];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$config_changes['delete'] = array_diff($all_target_names, $all_source_names);
|
||||
$config_changes['create'] = array_diff($all_source_names, $all_target_names);
|
||||
}
|
||||
|
||||
foreach (array_intersect($all_source_names, $all_target_names) as $name) {
|
||||
// Ignore manifest files
|
||||
if (substr($name, 0, 9) != 'manifest.') {
|
||||
$source_config_data = $source_storage->read($name);
|
||||
$target_config_data = $target_storage->read($name);
|
||||
if ($source_config_data !== $target_config_data) {
|
||||
$config_changes['change'][] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do not trigger subsequent synchronization operations if there are no
|
||||
// changes in any category.
|
||||
if (empty($config_changes['create']) && empty($config_changes['change']) && empty($config_changes['delete'])) {
|
||||
return FALSE;
|
||||
}
|
||||
return $config_changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an array of config file changes from a source storage to a target storage.
|
||||
*
|
||||
* @param array $config_changes
|
||||
* An array of changes to be written.
|
||||
* @param Drupal\Core\Config\StorageInterface $source_storage
|
||||
* The storage to synchronize configuration from.
|
||||
* @param Drupal\Core\Config\StorageInterface $target_storage
|
||||
* The storage to synchronize configuration to.
|
||||
*/
|
||||
function config_sync_changes(array $config_changes, StorageInterface $source_storage, StorageInterface $target_storage) {
|
||||
$target_context = drupal_container()->get('config.context.free');
|
||||
$factory = drupal_container()->get('config.factory');
|
||||
foreach (array('delete', 'create', 'change') as $op) {
|
||||
foreach ($config_changes[$op] as $name) {
|
||||
$config = new Config($name, $target_storage, $target_context);
|
||||
if ($op == 'delete') {
|
||||
$config->delete();
|
||||
}
|
||||
else {
|
||||
$data = $source_storage->read($name);
|
||||
$config->setData($data ? $data : array());
|
||||
$config->save();
|
||||
}
|
||||
$factory->reset($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports configuration into the active configuration.
|
||||
*
|
||||
* @return bool|null
|
||||
* TRUE if configuration was imported successfully, FALSE in case of a
|
||||
* synchronization error, or NULL if there are no changes to synchronize.
|
||||
*/
|
||||
function config_import() {
|
||||
// Retrieve a list of differences between staging and the active configuration.
|
||||
$source_storage = drupal_container()->get('config.storage.staging');
|
||||
$snapshot_storage = drupal_container()->get('config.storage.snapshot');
|
||||
$target_storage = drupal_container()->get('config.storage');
|
||||
|
||||
$config_changes = config_sync_get_changes($source_storage, $target_storage);
|
||||
if (empty($config_changes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!lock()->acquire(CONFIG_IMPORT_LOCK)) {
|
||||
// Another request is synchronizing configuration.
|
||||
// Return a negative result for UI purposes. We do not differentiate between
|
||||
// an actual synchronization error and a failed lock, because concurrent
|
||||
// synchronizations are an edge-case happening only when multiple developers
|
||||
// or site builders attempt to do it without coordinating.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$success = TRUE;
|
||||
try {
|
||||
// Use the override free context for config importing so that any overrides do
|
||||
// not change the data on import.
|
||||
config_context_enter('config.context.free');
|
||||
|
||||
$remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
|
||||
config_sync_changes($remaining_changes, $source_storage, $target_storage);
|
||||
config_import_create_snapshot($target_storage, $snapshot_storage);
|
||||
|
||||
// Exit the override free context.
|
||||
config_context_leave();
|
||||
}
|
||||
catch (ConfigException $e) {
|
||||
watchdog_exception('config_import', $e);
|
||||
$success = FALSE;
|
||||
}
|
||||
lock()->release(CONFIG_IMPORT_LOCK);
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration snapshot following a successful import.
|
||||
*
|
||||
* @param Drupal\Core\Config\StorageInterface $source_storage
|
||||
* The storage to synchronize configuration from.
|
||||
* @param Drupal\Core\Config\StorageInterface $target_storage
|
||||
* The storage to synchronize configuration to.
|
||||
*/
|
||||
function config_import_create_snapshot(StorageInterface $source_storage, StorageInterface $snapshot_storage) {
|
||||
$snapshot_storage->deleteAll();
|
||||
foreach ($source_storage->listAll() as $name) {
|
||||
$snapshot_storage->write($name, $source_storage->read($name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes MODULE_config_import() callbacks for configuration changes.
|
||||
*
|
||||
* @param array $config_changes
|
||||
* An array of changes to be loaded.
|
||||
* @param Drupal\Core\Config\StorageInterface $source_storage
|
||||
* The storage to synchronize configuration from.
|
||||
* @param Drupal\Core\Config\StorageInterface $target_storage
|
||||
* The storage to synchronize configuration to.
|
||||
*
|
||||
* @todo Add support for other extension types; e.g., themes etc.
|
||||
*/
|
||||
function config_import_invoke_owner(array $config_changes, StorageInterface $source_storage, StorageInterface $target_storage) {
|
||||
$factory = drupal_container()->get('config.factory');
|
||||
// Use the admin context for config importing so that any overrides do not
|
||||
// change the data on import.
|
||||
$free_context = drupal_container()->get('config.context.free');
|
||||
// Allow modules to take over configuration change operations for
|
||||
// higher-level configuration data.
|
||||
// First pass deleted, then new, and lastly changed configuration, in order to
|
||||
// handle dependencies correctly.
|
||||
$manager = Drupal::entityManager();
|
||||
foreach (array('delete', 'create', 'change') as $op) {
|
||||
foreach ($config_changes[$op] as $key => $name) {
|
||||
// Call to the configuration entity's storage controller to handle the
|
||||
// configuration change.
|
||||
$handled_by_module = FALSE;
|
||||
// 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, $target_storage, $free_context);
|
||||
$old_config->load();
|
||||
|
||||
$data = $source_storage->read($name);
|
||||
$new_config = new Config($name, $source_storage, $free_context);
|
||||
if ($data !== FALSE) {
|
||||
$new_config->setData($data);
|
||||
}
|
||||
|
||||
$method = 'import' . ucfirst($op);
|
||||
$handled_by_module = $manager->getStorageController($entity_type)->$method($name, $new_config, $old_config);
|
||||
}
|
||||
if (!empty($handled_by_module)) {
|
||||
$factory->reset($name);
|
||||
// Reset the manifest config object for the config entity.
|
||||
$entity_info = Drupal::entityManager()->getDefinition($entity_type);
|
||||
$factory->reset('manifest.' . $entity_info['config_prefix']);
|
||||
unset($config_changes[$op][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $config_changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of all config entity types provided by a module.
|
||||
*
|
||||
|
@ -424,6 +193,21 @@ function config_typed() {
|
|||
return drupal_container()->get('config.typed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration snapshot following a successful import.
|
||||
*
|
||||
* @param Drupal\Core\Config\StorageInterface $source_storage
|
||||
* The storage to synchronize configuration from.
|
||||
* @param Drupal\Core\Config\StorageInterface $snapshot_storage
|
||||
* The storage to synchronize configuration to.
|
||||
*/
|
||||
function config_import_create_snapshot(StorageInterface $source_storage, StorageInterface $snapshot_storage) {
|
||||
$snapshot_storage->deleteAll();
|
||||
foreach ($source_storage->listAll() as $name) {
|
||||
$snapshot_storage->write($name, $source_storage->read($name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a formatted diff of a named config between two storages.
|
||||
*
|
||||
|
|
|
@ -96,15 +96,13 @@ class ConfigFactory {
|
|||
*/
|
||||
public function reset($name = NULL) {
|
||||
if ($name) {
|
||||
// Reinitialize the configuration object in all contexts.
|
||||
// Clear the cached configuration object in all contexts.
|
||||
foreach ($this->getCacheKeys($name) as $cache_key) {
|
||||
$this->cache[$cache_key]->init();
|
||||
unset($this->cache[$cache_key]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
foreach ($this->cache as $config) {
|
||||
$config->init();
|
||||
}
|
||||
$this->cache = array();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,338 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigImporter.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Core\Config\Context\FreeConfigContext;
|
||||
use Drupal\Core\Entity\EntityManager;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Defines a configuration importer.
|
||||
*
|
||||
* A config importer imports the changes into the configuration system. To
|
||||
* determine which changes to import a StorageComparer in used.
|
||||
*
|
||||
* @see \Drupal\Core\Config\StorageComparerInterface
|
||||
*
|
||||
* The ConfigImporter has a identifier which is used to construct event names.
|
||||
* The events fired during an import are:
|
||||
* - 'config.importer.validate': Events listening can throw a
|
||||
* \Drupal\Core\Config\ConfigImporterException to prevent an import from
|
||||
* occurring.
|
||||
* @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
|
||||
* - 'config.importer.import': Events listening can react to a successful import.
|
||||
* @see \Drupal\Core\EventSubscriber\ConfigSnapshotSubscriber
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigImporterEvent
|
||||
*/
|
||||
class ConfigImporter {
|
||||
|
||||
/**
|
||||
* The name used to identify events and the lock.
|
||||
*/
|
||||
const ID = 'config.importer';
|
||||
|
||||
/**
|
||||
* The storage comparer used to discover configuration changes.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageComparerInterface
|
||||
*/
|
||||
protected $storageComparer;
|
||||
|
||||
/**
|
||||
* The event dispatcher used to notify subscribers.
|
||||
*
|
||||
* @var \Symfony\Component\EventDispatcher\EventDispatcher
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
* The configuration context.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Context\ContextInterface
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* The configuration factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactory
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The plugin manager for entities.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManager
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* The used lock backend instance.
|
||||
*
|
||||
* @var \Drupal\Core\Lock\LockBackendInterface
|
||||
*/
|
||||
protected $lock;
|
||||
|
||||
/**
|
||||
* List of changes processed by the import().
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $processed;
|
||||
|
||||
/**
|
||||
* Indicates changes to import have been validated.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $validated;
|
||||
|
||||
/**
|
||||
* Constructs a configuration import object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageComparerInterface $storage_comparer
|
||||
* A storage comparer object used to determin configuration changes and
|
||||
* access the source and target storage objects.
|
||||
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
|
||||
* The event dispatcher used to notify subscribers of config import events.
|
||||
* @param \Drupal\Core\Config\ConfigFactory $config_factory
|
||||
* The config factory that statically caches config objects.
|
||||
* @param \Drupal\Core\Entity\EntityManager $entity_manager
|
||||
* The entity manager used to import config entities.
|
||||
* @param \Drupal\Core\Lock\LockBackendInterface
|
||||
* The lock backend to ensure multiple imports do not occur at the same time.
|
||||
*/
|
||||
public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigFactory $config_factory, EntityManager $entity_manager, LockBackendInterface $lock) {
|
||||
$this->storageComparer = $storage_comparer;
|
||||
$this->eventDispatcher = $event_dispatcher;
|
||||
$this->configFactory = $config_factory;
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->lock = $lock;
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration storage comparer.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||
* Storage comparer object used to calculate configuration changes.
|
||||
*/
|
||||
public function getStorageComparer() {
|
||||
return $this->storageComparer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the storage comparer and processed list.
|
||||
*
|
||||
* @return \Drupal\Core\Config\ConfigImporter
|
||||
* The ConfigImporter instance.
|
||||
*/
|
||||
public function reset() {
|
||||
$this->storageComparer->reset();
|
||||
$this->processed = $this->storageComparer->getEmptyChangelist();
|
||||
$this->validated = FALSE;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any unprocessed changes.
|
||||
*
|
||||
* @param array $ops
|
||||
* The operations to check for changes. Defaults to all operations, i.e.
|
||||
* array('delete', 'create', 'update').
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if there are changes to process and FALSE if not.
|
||||
*/
|
||||
public function hasUnprocessedChanges($ops = array('delete', 'create', 'update')) {
|
||||
foreach ($ops as $op) {
|
||||
if (count($this->getUnprocessed($op))) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets list of processed changes.
|
||||
*
|
||||
* @return array
|
||||
* An array containing a list of processed changes.
|
||||
*/
|
||||
public function getProcessed() {
|
||||
return $this->processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a change as processed.
|
||||
*
|
||||
* @param string $op
|
||||
* The change operation performed, either delete, create or update.
|
||||
* @param string $name
|
||||
* The name of the configuration processed.
|
||||
*/
|
||||
protected function setProcessed($op, $name) {
|
||||
$this->processed[$op][] = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of unprocessed changes for a given operation.
|
||||
*
|
||||
* @param string $op
|
||||
* The change operation to get the unprocessed list for, either delete,
|
||||
* create or update.
|
||||
*
|
||||
* @return array
|
||||
* An array of configuration names.
|
||||
*/
|
||||
public function getUnprocessed($op) {
|
||||
return array_diff($this->storageComparer->getChangelist($op), $this->processed[$op]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports the changelist to the target storage.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\ConfigException
|
||||
*
|
||||
* @return \Drupal\Core\Config\ConfigImporter
|
||||
* The ConfigImporter instance.
|
||||
*/
|
||||
public function import() {
|
||||
if ($this->hasUnprocessedChanges()) {
|
||||
// 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));
|
||||
}
|
||||
$this->importInvokeOwner();
|
||||
$this->importConfig();
|
||||
// Allow modules to react to a import.
|
||||
$this->notify('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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches validate event for a ConfigImporter object.
|
||||
*
|
||||
* Events should throw a \Drupal\Core\Config\ConfigImporterException to
|
||||
* prevent an import from occurring.
|
||||
*/
|
||||
public function validate() {
|
||||
if (!$this->validated) {
|
||||
$this->notify('validate');
|
||||
$this->validated = TRUE;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an array of config changes from the source to the target storage.
|
||||
*/
|
||||
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);
|
||||
if ($op == 'delete') {
|
||||
$config->delete();
|
||||
}
|
||||
else {
|
||||
$data = $this->storageComparer->getSourceStorage()->read($name);
|
||||
$config->setData($data ? $data : array());
|
||||
$config->save();
|
||||
}
|
||||
$this->setProcessed($op, $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes import* methods on configuration entity storage controllers.
|
||||
*
|
||||
* Allow modules to take over configuration change operations for higher-level
|
||||
* configuration data.
|
||||
*
|
||||
* @todo Add support for other extension types; e.g., themes etc.
|
||||
*/
|
||||
protected function importInvokeOwner() {
|
||||
// First pass deleted, then new, and lastly changed configuration, in order
|
||||
// to handle dependencies correctly.
|
||||
foreach (array('delete', 'create', 'update') as $op) {
|
||||
foreach ($this->getUnprocessed($op) as $name) {
|
||||
// Call to the configuration entity's storage controller to handle the
|
||||
// configuration change.
|
||||
$handled_by_module = FALSE;
|
||||
// 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);
|
||||
$old_config->load();
|
||||
|
||||
$data = $this->storageComparer->getSourceStorage()->read($name);
|
||||
$new_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->context);
|
||||
if ($data !== FALSE) {
|
||||
$new_config->setData($data);
|
||||
}
|
||||
|
||||
$method = 'import' . ucfirst($op);
|
||||
$handled_by_module = $this->entityManager->getStorageController($entity_type)->$method($name, $new_config, $old_config);
|
||||
}
|
||||
if (!empty($handled_by_module)) {
|
||||
$this->setProcessed($op, $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a config importer event.
|
||||
*
|
||||
* @param string $event_name
|
||||
* The name of the config importer event to dispatch.
|
||||
*/
|
||||
protected function notify($event_name) {
|
||||
$this->eventDispatcher->dispatch(static::ID . '.' . $event_name, new ConfigImporterEvent($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a import is already running.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if an import is already running, FALSE if not.
|
||||
*/
|
||||
public function alreadyImporting() {
|
||||
return !$this->lock->lockMayBeAvailable(static::ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier for events and locks.
|
||||
*
|
||||
* @return string
|
||||
* The identifier for events and locks.
|
||||
*/
|
||||
public function getId() {
|
||||
return static::ID;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigImporterEvent.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
class ConfigImporterEvent extends Event {
|
||||
/**
|
||||
* Configuration import object.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigImporter
|
||||
*/
|
||||
protected $configImporter;
|
||||
|
||||
/**
|
||||
* Constructs ConfigImporterEvent.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigImporter $config_importer
|
||||
* A config import object to notify listeners about.
|
||||
*/
|
||||
public function __construct(ConfigImporter $config_importer) {
|
||||
$this->configImporter = $config_importer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the config import object.
|
||||
*
|
||||
* @return \Drupal\Core\Config\ConfigImporter
|
||||
* The ConfigImporter object.
|
||||
*/
|
||||
public function getConfigImporter() {
|
||||
return $this->configImporter;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigImporterException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Exception thrown when a config import fails.
|
||||
*/
|
||||
class ConfigImporterException extends ConfigException {}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigInstaller.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Defines a configuration installer.
|
||||
*
|
||||
* A config installer imports the changes into the configuration system during
|
||||
* module installs.
|
||||
*
|
||||
* The ConfigInstaller has a identifier which is used to construct event names.
|
||||
* The events fired during an import are:
|
||||
* - 'config.installer.validate': Events listening can throw a
|
||||
* \Drupal\Core\Config\ConfigImporterException to prevent an import from
|
||||
* occurring.
|
||||
* @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
|
||||
* - 'config.installer.import': Events listening can react to a successful import.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigImporter
|
||||
*/
|
||||
class ConfigInstaller extends ConfigImporter {
|
||||
|
||||
/**
|
||||
* The name used to identify events and the lock.
|
||||
*/
|
||||
const ID = 'config.installer';
|
||||
|
||||
}
|
|
@ -521,7 +521,7 @@ class ConfigStorageController implements EntityStorageControllerInterface {
|
|||
}
|
||||
|
||||
/**
|
||||
* Update configuration upon synchronizing configuration changes.
|
||||
* Updates configuration upon synchronizing configuration changes.
|
||||
*
|
||||
* This callback is invoked when configuration is synchronized between storages
|
||||
* and allows a module to take over the synchronization of configuration data.
|
||||
|
@ -533,7 +533,7 @@ class ConfigStorageController implements EntityStorageControllerInterface {
|
|||
* @param \Drupal\Core\Config\Config $old_config
|
||||
* A configuration object containing the old configuration data.
|
||||
*/
|
||||
public function importChange($name, Config $new_config, Config $old_config) {
|
||||
public function importUpdate($name, Config $new_config, Config $old_config) {
|
||||
$id = static::getIDFromConfigName($name, $this->entityInfo['config_prefix']);
|
||||
$entities = $this->load(array($id));
|
||||
$entity = $entities[$id];
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\StorageComparer.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Defines a config storage comparer.
|
||||
*/
|
||||
class StorageComparer implements StorageComparerInterface {
|
||||
|
||||
/**
|
||||
* The source storage used to discover configuration changes.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $sourceStorage;
|
||||
|
||||
/**
|
||||
* The target storage used to write configuration changes.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $targetStorage;
|
||||
|
||||
/**
|
||||
* List of changes to between the source storage and the target storage.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $changelist;
|
||||
|
||||
/**
|
||||
* Lists all the configuration object names in the source storage.
|
||||
*
|
||||
* @see \Drupal\Core\Config\StorageComparer::getSourceNames()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceNames = array();
|
||||
|
||||
/**
|
||||
* Lists all the configuration object names in the target storage.
|
||||
*
|
||||
* @see \Drupal\Core\Config\StorageComparer::getTargetNames()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $targetNames = array();
|
||||
|
||||
/**
|
||||
* Constructs the Configuration storage comparer.
|
||||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $source_storage
|
||||
* Storage controller object used to read configuration.
|
||||
* @param \Drupal\Core\Config\StorageInterface $target_storage
|
||||
* Storage controller object used to write configuration.
|
||||
*/
|
||||
public function __construct(StorageInterface $source_storage, StorageInterface $target_storage) {
|
||||
$this->sourceStorage = $source_storage;
|
||||
$this->targetStorage = $target_storage;
|
||||
$this->changelist = $this->getEmptyChangelist();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSourceStorage() {
|
||||
return $this->sourceStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTargetStorage() {
|
||||
return $this->targetStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEmptyChangelist() {
|
||||
return array(
|
||||
'create' => array(),
|
||||
'update' => array(),
|
||||
'delete' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChangelist($op = NULL) {
|
||||
if ($op) {
|
||||
return $this->changelist[$op];
|
||||
}
|
||||
return $this->changelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addChangeList($op, array $changes) {
|
||||
// Only add changes that aren't already listed.
|
||||
$changes = array_diff($changes, $this->changelist[$op]);
|
||||
$this->changelist[$op] = array_merge($this->changelist[$op], $changes);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createChangelist() {
|
||||
return $this
|
||||
->addChangelistCreate()
|
||||
->addChangelistUpdate()
|
||||
->addChangelistDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addChangelistDelete() {
|
||||
return $this->addChangeList('delete', array_diff($this->getTargetNames(), $this->getSourceNames()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addChangelistCreate() {
|
||||
return $this->addChangeList('create', array_diff($this->getSourceNames(), $this->getTargetNames()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addChangelistUpdate() {
|
||||
foreach (array_intersect($this->getSourceNames(), $this->getTargetNames()) as $name) {
|
||||
// Ignore manifest files.
|
||||
if (substr($name, 0, 9) != 'manifest.') {
|
||||
$source_config_data = $this->sourceStorage->read($name);
|
||||
$target_config_data = $this->targetStorage->read($name);
|
||||
if ($source_config_data !== $target_config_data) {
|
||||
$this->addChangeList('update', array($name));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset() {
|
||||
$this->changelist = $this->getEmptyChangelist();
|
||||
$this->sourceNames = $this->targetNames = array();
|
||||
return $this->createChangelist();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasChanges($ops = array('delete', 'create', 'update')) {
|
||||
foreach ($ops as $op) {
|
||||
if (!empty($this->changelist[$op])) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the configuration names in the source storage.
|
||||
*
|
||||
* @return array
|
||||
* List of all the configuration names in the source storage.
|
||||
*/
|
||||
protected function getSourceNames() {
|
||||
if (empty($this->sourceNames)) {
|
||||
$this->sourceNames = $this->sourceStorage->listAll();
|
||||
}
|
||||
return $this->sourceNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the configuration names in the target storage.
|
||||
*
|
||||
* @return array
|
||||
* List of all the configuration names in the target storage.
|
||||
*/
|
||||
protected function getTargetNames() {
|
||||
if (empty($this->targetNames)) {
|
||||
$this->targetNames = $this->targetStorage->listAll();
|
||||
}
|
||||
return $this->targetNames;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\StorageComparerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Defines an interface for comparison of configuration storage objects.
|
||||
*/
|
||||
interface StorageComparerInterface {
|
||||
|
||||
/**
|
||||
* Gets the configuration source storage.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface
|
||||
* Storage controller object used to read configuration.
|
||||
*/
|
||||
public function getSourceStorage();
|
||||
|
||||
/**
|
||||
* Gets the configuration target storage.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface
|
||||
* Storage controller object used to write configuration.
|
||||
*/
|
||||
public function getTargetStorage();
|
||||
|
||||
/**
|
||||
* Gets an empty changelist.
|
||||
*
|
||||
* @return array
|
||||
* An empty changelist array.
|
||||
*/
|
||||
public function getEmptyChangelist();
|
||||
|
||||
/**
|
||||
* Gets the list of differences to import.
|
||||
*
|
||||
* @param string $op
|
||||
* (optional) A change operation. Either delete, create or update. If
|
||||
* supplied the returned list will be limited to this operation.
|
||||
*
|
||||
* @return array
|
||||
* An array of config changes that are yet to be imported.
|
||||
*/
|
||||
public function getChangelist($op = NULL);
|
||||
|
||||
/**
|
||||
* Adds changes to the changelist.
|
||||
*
|
||||
* @param string $op
|
||||
* The change operation performed. Either delete, create or update.
|
||||
* @param array $changes
|
||||
* Array of changes to add the changelist.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||
* An object which implements the StorageComparerInterface.
|
||||
*/
|
||||
public function addChangeList($op, array $changes);
|
||||
|
||||
/**
|
||||
* Add differences between source and target configuration storage to changelist.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||
* An object which implements the StorageComparerInterface.
|
||||
*/
|
||||
public function createChangelist();
|
||||
|
||||
/**
|
||||
* Creates the delete changelist.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||
* An object which implements the StorageComparerInterface.
|
||||
*/
|
||||
public function addChangelistDelete();
|
||||
|
||||
/**
|
||||
* Creates the create changelist.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||
* An object which implements the StorageComparerInterface.
|
||||
*/
|
||||
public function addChangelistCreate();
|
||||
|
||||
/**
|
||||
* Creates the update changelist.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||
* An object which implements the StorageComparerInterface.
|
||||
*/
|
||||
public function addChangelistUpdate();
|
||||
|
||||
/**
|
||||
* Recalculates the differences.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageComparerInterface
|
||||
* An object which implements the StorageComparerInterface.
|
||||
*/
|
||||
public function reset();
|
||||
|
||||
/**
|
||||
* Checks if there are any operations with changes to process.
|
||||
*
|
||||
* Until the changelist has been calculated this will always be FALSE.
|
||||
*
|
||||
* @see \Drupal\Core\Config\StorageComparerInterface::createChangelist().
|
||||
*
|
||||
* @param array $ops
|
||||
* The operations to check for changes. Defaults to all operations, i.e.
|
||||
* array('delete', 'create', 'update').
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if there are changes to process and FALSE if not.
|
||||
*/
|
||||
public function hasChanges($ops = array('delete', 'create', 'update'));
|
||||
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\StorageComparerManifest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
/**
|
||||
* Defines a config storage comparer that uses config entity manifests.
|
||||
*
|
||||
* Config entities maintain 'manifest' files that list the objects they are
|
||||
* currently handling. Each file is a simple indexed array of config object
|
||||
* names. In order to generate a list of objects that have been created or
|
||||
* deleted we need to open these files in both the source and target storage,
|
||||
* generate an array of the objects, and compare them.
|
||||
*/
|
||||
class StorageComparerManifest extends StorageComparer {
|
||||
|
||||
/**
|
||||
* List of config entities managed by manifests in the source storage.
|
||||
*
|
||||
* @see \Drupal\Core\Config\StorageComparerManifest::getSourceManifestData()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceManifestData = array();
|
||||
|
||||
/**
|
||||
* List of config entities managed by manifests in the target storage.
|
||||
*
|
||||
* @see \Drupal\Core\Config\StorageComparerManifest::getTargetManifestData()
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $targetManifestData = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addChangelistDelete() {
|
||||
foreach (array_diff_key($this->getTargetManifestData(), $this->getSourceManifestData()) as $value) {
|
||||
$this->addChangeList('delete', array($value['name']));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addChangelistCreate() {
|
||||
foreach (array_diff_key($this->getSourceManifestData(), $this->getTargetManifestData()) as $value) {
|
||||
$this->addChangeList('create', array($value['name']));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of config entities from the source storage's manifest files.
|
||||
*
|
||||
* @return array
|
||||
* The list of config entities in the source storage whose entity type has a
|
||||
* manifest in the source storage.
|
||||
*/
|
||||
protected function getSourceManifestData() {
|
||||
if (empty($this->sourceManifestData)) {
|
||||
foreach ($this->getSourceStorage()->listAll('manifest') as $name) {
|
||||
if ($source_manifest_data = $this->getSourceStorage()->read($name)) {
|
||||
$this->sourceManifestData = array_merge($this->sourceManifestData, $source_manifest_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->sourceManifestData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of config entities from the target storage's manifest files.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigImporter::getSourceManifestData()
|
||||
*
|
||||
* @return array
|
||||
* The list of config entities in the target storage whose entity type has a
|
||||
* manifest in the source storage.
|
||||
*/
|
||||
protected function getTargetManifestData() {
|
||||
if (empty($this->targetManifestData)) {
|
||||
foreach ($this->getSourceStorage()->listAll('manifest') as $name) {
|
||||
if ($target_manifest_data = $this->targetStorage->read($name)) {
|
||||
$this->targetManifestData = array_merge($this->targetManifestData, $target_manifest_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->targetManifestData;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset() {
|
||||
$this->sourceManifestData = $this->targetManifestData = array();
|
||||
return parent::reset();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\EventSubscriber\ConfigImportSubscriber.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Config\ConfigImporterEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
|
||||
/**
|
||||
* Config import subscriber for config import events.
|
||||
*/
|
||||
class ConfigImportSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Validates the configuration to be imported.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigImporterEvent $event
|
||||
* The Event to process.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\ConfigNameException
|
||||
*/
|
||||
public function onConfigImporterValidate(ConfigImporterEvent $event) {
|
||||
foreach (array('delete', 'create', 'update') as $op) {
|
||||
foreach ($event->getConfigImporter()->getUnprocessed($op) as $name) {
|
||||
Config::validateName($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the methods in this class that should be listeners.
|
||||
*
|
||||
* @return array
|
||||
* An array of event listener definitions.
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
$events['config.importer.validate'][] = array('onConfigImporterValidate', 40);
|
||||
$events['config.installer.validate'][] = array('onConfigImporterValidate', 40);
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\EventSubscriber\ConfigSnapshotSubscriber.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\Config\ConfigImporterEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Create a snapshot when config is imported.
|
||||
*/
|
||||
class ConfigSnapshotSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The source storage used to discover configuration changes.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $sourceStorage;
|
||||
|
||||
/**
|
||||
* The snapshot storage used to write configuration changes.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $snapshotStorage;
|
||||
|
||||
/**
|
||||
* Constructs the ConfigSnapshotSubscriber object.
|
||||
*
|
||||
* @param StorageInterface $source_storage
|
||||
* The source storage used to discover configuration changes.
|
||||
* @param StorageInterface $snapshot_storage
|
||||
* The snapshot storage used to write configuration changes.
|
||||
*/
|
||||
public function __construct(StorageInterface $source_storage, StorageInterface $snapshot_storage) {
|
||||
$this->sourceStorage = $source_storage;
|
||||
$this->snapshotStorage = $snapshot_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a config snapshot.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigImporterEvent $event
|
||||
* The Event to process.
|
||||
*/
|
||||
public function onConfigImporterImport(ConfigImporterEvent $event) {
|
||||
config_import_create_snapshot($this->sourceStorage, $this->snapshotStorage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the methods in this class that should be listeners.
|
||||
*
|
||||
* @return array
|
||||
* An array of event listener definitions.
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
$events['config.importer.import'][] = array('onConfigImporterImport', 40);
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -5,9 +5,12 @@
|
|||
* Admin page callbacks for the config module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\Ajax\AjaxResponse;
|
||||
use Drupal\Core\Ajax\OpenModalDialogCommand;
|
||||
use Drupal\Core\Config\ConfigException;
|
||||
use Drupal\Core\Config\ConfigImporter;
|
||||
use Drupal\Core\Config\StorageComparerManifest;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
|
||||
/**
|
||||
* Helper function to construct the storage changes in a configuration synchronization form.
|
||||
|
@ -16,12 +19,13 @@ use Drupal\Core\Ajax\OpenModalDialogCommand;
|
|||
* The form structure to add to. Passed by reference.
|
||||
* @param array $form_state
|
||||
* The current state of the form. Passed by reference.
|
||||
* @param Drupal\Core\Config\StorageInterface $source_storage
|
||||
* @param \Drupal\Core\Config\StorageInterface $source_storage
|
||||
* The source storage to retrieve differences from.
|
||||
* @param Drupal\Core\Config\StorageInterface $target_storage
|
||||
* The target storage to compare differences to.
|
||||
*
|
||||
* @return array
|
||||
* The form with the configuration storage changes.
|
||||
*/
|
||||
function config_admin_sync_form(array &$form, array &$form_state, StorageInterface $source_storage, StorageInterface $target_storage) {
|
||||
function config_admin_sync_form(array &$form, array &$form_state, StorageInterface $source_storage) {
|
||||
$source_list = $source_storage->listAll();
|
||||
if (empty($source_list)) {
|
||||
$form['no_changes'] = array(
|
||||
|
@ -31,18 +35,23 @@ function config_admin_sync_form(array &$form, array &$form_state, StorageInterfa
|
|||
return $form;
|
||||
}
|
||||
|
||||
$config_changes = config_sync_get_changes($source_storage, $target_storage);
|
||||
if (empty($config_changes)) {
|
||||
$config_comparer = new StorageComparerManifest(Drupal::service('config.storage.staging'), Drupal::service('config.storage'));
|
||||
if (!$config_comparer->createChangelist()->hasChanges()) {
|
||||
$form['no_changes'] = array(
|
||||
'#markup' => t('There are no configuration changes.'),
|
||||
);
|
||||
$form['actions']['#access'] = FALSE;
|
||||
return $form;
|
||||
}
|
||||
else {
|
||||
// Store the comparer for use in the submit.
|
||||
$form_state['storage_comparer'] = $config_comparer;
|
||||
}
|
||||
|
||||
// Add the AJAX library to the form for dialog support.
|
||||
$form['#attached']['library'][] = array('system', 'drupal.ajax');
|
||||
|
||||
foreach ($config_changes as $config_change_type => $config_files) {
|
||||
foreach ($config_comparer->getChangelist() as $config_change_type => $config_files) {
|
||||
if (empty($config_files)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -58,7 +67,7 @@ function config_admin_sync_form(array &$form, array &$form_state, StorageInterfa
|
|||
$form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count new', '@count new');
|
||||
break;
|
||||
|
||||
case 'change':
|
||||
case 'update':
|
||||
$form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count changed', '@count changed');
|
||||
break;
|
||||
|
||||
|
@ -122,23 +131,37 @@ function config_admin_import_form($form, &$form_state) {
|
|||
* Form submission handler for config_admin_import_form().
|
||||
*/
|
||||
function config_admin_import_form_submit($form, &$form_state) {
|
||||
if (!lock()->lockMayBeAvailable(CONFIG_IMPORT_LOCK)) {
|
||||
$config_importer = new ConfigImporter(
|
||||
$form_state['storage_comparer'],
|
||||
Drupal::service('event_dispatcher'),
|
||||
Drupal::service('config.factory'),
|
||||
Drupal::entityManager(),
|
||||
Drupal::lock()
|
||||
);
|
||||
if ($config_importer->alreadyImporting()) {
|
||||
drupal_set_message(t('Another request may be synchronizing configuration already.'));
|
||||
}
|
||||
else if (config_import()) {
|
||||
// Once a sync completes, we empty the staging directory. This prevents
|
||||
// changes from being accidentally overwritten by stray files getting
|
||||
// imported later.
|
||||
$source_storage = drupal_container()->get('config.storage.staging');
|
||||
foreach ($source_storage->listAll() as $name) {
|
||||
$source_storage->delete($name);
|
||||
else{
|
||||
try {
|
||||
$config_importer->import();
|
||||
drupal_flush_all_caches();
|
||||
drupal_set_message(t('The configuration was imported successfully.'));
|
||||
|
||||
// Once a sync completes, we empty the staging directory. This prevents
|
||||
// changes from being accidentally overwritten by stray files getting
|
||||
// imported later.
|
||||
$source_storage = $config_importer->getStorageComparer()->getSourceStorage();
|
||||
foreach ($source_storage->listAll() as $name) {
|
||||
$source_storage->delete($name);
|
||||
}
|
||||
}
|
||||
catch (ConfigException $e) {
|
||||
// Return a negative result for UI purposes. We do not differentiate between
|
||||
// an actual synchronization error and a failed lock, because concurrent
|
||||
// synchronizations are an edge-case happening only when multiple developers
|
||||
// or site builders attempt to do it without coordinating.
|
||||
watchdog_exception('config_import', $e);
|
||||
drupal_set_message(t('The import failed due to an error. Any errors have been logged.'), 'error');
|
||||
}
|
||||
|
||||
drupal_flush_all_caches();
|
||||
|
||||
drupal_set_message(t('The configuration was imported successfully.'));
|
||||
}
|
||||
else {
|
||||
drupal_set_message(t('The import failed due to an error. Any errors have been logged.'), 'error');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,14 @@ use Drupal\simpletest\DrupalUnitTestBase;
|
|||
* Tests CRUD operations on configuration objects.
|
||||
*/
|
||||
class ConfigCRUDTest extends DrupalUnitTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system');
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'CRUD operations',
|
||||
|
@ -193,8 +201,15 @@ class ConfigCRUDTest extends DrupalUnitTestBase {
|
|||
$manifest_data['new']['name'] = 'invalid';
|
||||
$staging->write('manifest.invalid_object_name', $manifest_data);
|
||||
|
||||
// Assert that config_import returns false indicating a failure.
|
||||
$this->assertFalse(config_import(), "Config import failed when trying to importing an object with an invalid name");
|
||||
// Verify that an exception is thrown when importing.
|
||||
$message = 'Expected ConfigNameException was thrown when attempting to sync invalid configuration.';
|
||||
try {
|
||||
$this->configImporter()->import();
|
||||
$this->fail($message);
|
||||
}
|
||||
catch (ConfigNameException $e) {
|
||||
$this->pass($message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,17 +2,26 @@
|
|||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\config\Tests\ConfigImportTest.
|
||||
* Contains \Drupal\config\Tests\ConfigImporterTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\config\Tests;
|
||||
|
||||
use Drupal\Core\Config\ConfigImporter;
|
||||
use Drupal\Core\Config\StorageComparerManifest;
|
||||
use Drupal\simpletest\DrupalUnitTestBase;
|
||||
|
||||
/**
|
||||
* Tests importing configuration from files into active configuration.
|
||||
*/
|
||||
class ConfigImportTest extends DrupalUnitTestBase {
|
||||
class ConfigImporterTest extends DrupalUnitTestBase {
|
||||
|
||||
/**
|
||||
* Config Importer object used for testing.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigImporter
|
||||
*/
|
||||
protected $configImporter;
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
|
@ -39,6 +48,18 @@ class ConfigImportTest extends DrupalUnitTestBase {
|
|||
// variable being used for recording hook invocations by this test already,
|
||||
// so it has to be cleared out manually.
|
||||
unset($GLOBALS['hook_config_test']);
|
||||
|
||||
// Set up the ConfigImporter object for testing.
|
||||
$config_comparer = new StorageComparerManifest(
|
||||
$this->container->get('config.storage.staging'),
|
||||
$this->container->get('config.storage'));
|
||||
$this->configImporter = new ConfigImporter(
|
||||
$config_comparer->createChangelist(),
|
||||
$this->container->get('event_dispatcher'),
|
||||
$this->container->get('config.factory'),
|
||||
$this->container->get('plugin.manager.entity'),
|
||||
$this->container->get('lock')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,7 +91,7 @@ class ConfigImportTest extends DrupalUnitTestBase {
|
|||
// Create an empty manifest to delete the configuration object.
|
||||
$staging->write('manifest.config_test.dynamic', array());
|
||||
// Import.
|
||||
config_import();
|
||||
$this->configImporter->reset()->import();
|
||||
|
||||
// Verify the values have disappeared.
|
||||
$this->assertIdentical($storage->read($dynamic_name), FALSE);
|
||||
|
@ -87,7 +108,7 @@ class ConfigImportTest extends DrupalUnitTestBase {
|
|||
$this->assertTrue(isset($GLOBALS['hook_config_test']['delete']));
|
||||
|
||||
// Verify that there is nothing more to import.
|
||||
$this->assertFalse(config_sync_get_changes($staging, $storage));
|
||||
$this->assertFalse($this->configImporter->hasUnprocessedChanges());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -123,7 +144,7 @@ class ConfigImportTest extends DrupalUnitTestBase {
|
|||
$this->assertIdentical($staging->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
|
||||
|
||||
// Import.
|
||||
config_import();
|
||||
$this->configImporter->reset()->import();
|
||||
|
||||
// Verify the values appeared.
|
||||
$config = config($dynamic_name);
|
||||
|
@ -138,7 +159,7 @@ class ConfigImportTest extends DrupalUnitTestBase {
|
|||
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
|
||||
|
||||
// Verify that there is nothing more to import.
|
||||
$this->assertFalse(config_sync_get_changes($staging, $storage));
|
||||
$this->assertFalse($this->configImporter->hasUnprocessedChanges());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -174,7 +195,7 @@ class ConfigImportTest extends DrupalUnitTestBase {
|
|||
$this->assertIdentical($config->get('label'), 'Default');
|
||||
|
||||
// Import.
|
||||
config_import();
|
||||
$this->configImporter->reset()->import();
|
||||
|
||||
// Verify the values were updated.
|
||||
$config = config($name);
|
||||
|
@ -195,7 +216,7 @@ class ConfigImportTest extends DrupalUnitTestBase {
|
|||
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
|
||||
|
||||
// Verify that there is nothing more to import.
|
||||
$this->assertFalse(config_sync_get_changes($staging, $storage));
|
||||
$this->assertFalse($this->configImporter->hasUnprocessedChanges());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -112,7 +112,8 @@ class ConfigImportUITest extends WebTestBase {
|
|||
$this->assertNoText(t('There are no configuration changes.'));
|
||||
|
||||
// Acquire a fake-lock on the import mechanism.
|
||||
lock()->acquire('config_import');
|
||||
$config_importer_lock = $this->configImporter()->getId();
|
||||
$this->container->get('lock')->acquire($config_importer_lock);
|
||||
|
||||
// Attempt to import configuration and verify that an error message appears.
|
||||
$this->drupalPost(NULL, array(), t('Import all'));
|
||||
|
@ -120,7 +121,7 @@ class ConfigImportUITest extends WebTestBase {
|
|||
$this->assertText(t('Another request may be synchronizing configuration already.'));
|
||||
|
||||
// Release the lock, just to keep testing sane.
|
||||
lock()->release('config_import');
|
||||
$this->container->get('lock')->release($config_importer_lock);
|
||||
|
||||
// Verify site name has not changed.
|
||||
$this->assertNotEqual($new_site_name, config('system.site')->get('name'));
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
namespace Drupal\config\Tests;
|
||||
|
||||
use Drupal\simpletest\DrupalUnitTestBase;
|
||||
use Drupal\Core\Config\Context\ConfigContext;
|
||||
|
||||
/**
|
||||
* Tests configuration overrides via $conf in settings.php.
|
||||
|
@ -118,7 +117,7 @@ class ConfigOverrideTest extends DrupalUnitTestBase {
|
|||
$staging->write('config_test.system', $expected_new_data);
|
||||
|
||||
// Import changed data from staging to active.
|
||||
config_import();
|
||||
$this->configImporter()->import();
|
||||
$data = $active->read('config_test.system');
|
||||
|
||||
// Verify that the new configuration data exists. Have to read storage
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\config\Tests;
|
||||
|
||||
use Drupal\Core\Config\StorageComparer;
|
||||
use Drupal\simpletest\DrupalUnitTestBase;
|
||||
|
||||
/**
|
||||
|
@ -45,21 +46,24 @@ class ConfigSnapshotTest extends DrupalUnitTestBase {
|
|||
$config_key = 'foo';
|
||||
$new_data = 'foobar';
|
||||
|
||||
$active_snapshot_comparer = new StorageComparer($active, $snapshot);
|
||||
$staging_snapshot_comparer = new StorageComparer($staging, $snapshot);
|
||||
|
||||
// Verify that we have an initial snapshot that matches the active
|
||||
// configuration. This has to be true as no config should be installed.
|
||||
$this->assertFalse(config_sync_get_changes($snapshot, $active, FALSE));
|
||||
$this->assertFalse($active_snapshot_comparer->createChangelist()->hasChanges());
|
||||
|
||||
// Install the default config.
|
||||
config_install_default_config('module', 'config_test');
|
||||
// Although we have imported config this has not affected the snapshot.
|
||||
$this->assertTrue(config_sync_get_changes($snapshot, $active, FALSE));
|
||||
$this->assertTrue($active_snapshot_comparer->reset()->hasChanges());
|
||||
|
||||
// Update the config snapshot.
|
||||
config_import_create_snapshot($active, $snapshot);
|
||||
|
||||
// The snapshot and active config should now contain the same config
|
||||
// objects.
|
||||
$this->assertFalse(config_sync_get_changes($snapshot, $active, FALSE));
|
||||
$this->assertFalse($active_snapshot_comparer->reset()->hasChanges());
|
||||
|
||||
// Change a configuration value in staging.
|
||||
$staging_data = config($config_name)->get();
|
||||
|
@ -67,20 +71,19 @@ class ConfigSnapshotTest extends DrupalUnitTestBase {
|
|||
$staging->write($config_name, $staging_data);
|
||||
|
||||
// Verify that active and snapshot match, and that staging doesn't match
|
||||
// either of them.
|
||||
$this->assertFalse(config_sync_get_changes($snapshot, $active, FALSE));
|
||||
$this->assertTrue(config_sync_get_changes($snapshot, $staging, FALSE));
|
||||
$this->assertTrue(config_sync_get_changes($staging, $active, FALSE));
|
||||
// active.
|
||||
$this->assertFalse($active_snapshot_comparer->reset()->hasChanges());
|
||||
$this->assertTrue($staging_snapshot_comparer->createChangelist()->hasChanges());
|
||||
|
||||
// Import changed data from staging to active.
|
||||
config_import();
|
||||
$this->configImporter()->import();
|
||||
|
||||
// Verify changed config was properly imported.
|
||||
$this->assertIdentical(config($config_name)->get($config_key), $new_data);
|
||||
|
||||
// Verify that a new snapshot was created which and that it matches
|
||||
// the active config.
|
||||
$this->assertFalse(config_sync_get_changes($snapshot, $active, FALSE));
|
||||
$this->assertFalse($active_snapshot_comparer->reset()->hasChanges());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,13 +26,13 @@ class ConfigTestStorageController extends ConfigStorageController {
|
|||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Core\Config\Entity\ConfigStorageController::importChange().
|
||||
* Overrides \Drupal\Core\Config\Entity\ConfigStorageController::importUpdate().
|
||||
*/
|
||||
public function importChange($name, Config $new_config, Config $old_config) {
|
||||
public function importUpdate($name, Config $new_config, Config $old_config) {
|
||||
// Set a global value we can check in test code.
|
||||
$GLOBALS['hook_config_import'] = __METHOD__;
|
||||
|
||||
return parent::importChange($name, $new_config, $old_config);
|
||||
return parent::importUpdate($name, $new_config, $old_config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -54,7 +54,7 @@ class FieldImportChangeTest extends FieldUnitTestBase {
|
|||
$staging->write($instance_config_name, $instance);
|
||||
|
||||
// Import the content of the staging directory.
|
||||
config_import();
|
||||
$this->configImporter()->import();
|
||||
|
||||
// Check that the updated config was correctly imported.
|
||||
$instance = entity_load('field_instance', $instance_id);
|
||||
|
|
|
@ -69,7 +69,7 @@ class FieldImportCreateTest extends FieldUnitTestBase {
|
|||
$staging->write($instance_manifest_name, $instance_manifest);
|
||||
|
||||
// Import the content of the staging directory.
|
||||
config_import();
|
||||
$this->configImporter()->import();
|
||||
|
||||
// Check that the field and instance were created.
|
||||
$field = entity_load('field_entity', $field_id);
|
||||
|
|
|
@ -66,7 +66,7 @@ class FieldImportDeleteTest extends FieldUnitTestBase {
|
|||
$staging->write($instance_manifest_name, $instance_manifest);
|
||||
|
||||
// Import the content of the staging directory.
|
||||
config_import();
|
||||
$this->configImporter()->import();
|
||||
|
||||
// Check that the field and instance are gone.
|
||||
$field = entity_load('field_entity', $field_id, TRUE);
|
||||
|
|
|
@ -38,8 +38,8 @@ abstract class FieldUnitTestBase extends DrupalUnitTestBase {
|
|||
$this->installSchema('entity_test', 'entity_test');
|
||||
$this->installSchema('field_test', array('test_entity', 'test_entity_revision', 'test_entity_bundle'));
|
||||
|
||||
// Set default storage backend.
|
||||
$this->installConfig(array('field'));
|
||||
// Set default storage backend and configure the theme system.
|
||||
$this->installConfig(array('field', 'system'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -367,7 +367,7 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
|
|||
unset($manifest_data[$style_name]);
|
||||
$staging = $this->container->get('config.storage.staging');
|
||||
$staging->write('manifest.image.style', $manifest_data);
|
||||
config_import();
|
||||
$this->configImporter()->import();
|
||||
|
||||
$this->assertFalse(entity_load('image_style', $style_name), 'Style deleted after config import.');
|
||||
$this->assertEqual($this->getImageCount($style), 0, 'Image style was flushed after being deleted by config import.');
|
||||
|
|
|
@ -9,6 +9,8 @@ namespace Drupal\simpletest;
|
|||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Component\Utility\Settings;
|
||||
use Drupal\Core\Config\ConfigImporter;
|
||||
use Drupal\Core\Config\StorageComparerManifest;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Database\ConnectionNotDefinedException;
|
||||
use Drupal\Core\DrupalKernel;
|
||||
|
@ -167,6 +169,13 @@ abstract class TestBase {
|
|||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* The config importer that can used in a test.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigImporter
|
||||
*/
|
||||
protected $configImporter;
|
||||
|
||||
/**
|
||||
* Constructor for Test.
|
||||
*
|
||||
|
@ -1272,4 +1281,28 @@ abstract class TestBase {
|
|||
public static function filePreDeleteCallback($path) {
|
||||
chmod($path, 0700);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ConfigImporter object to import test importing of configuration.
|
||||
*
|
||||
* @return \Drupal\Core\Config\ConfigImporter
|
||||
* The ConfigImporter object.
|
||||
*/
|
||||
public function configImporter() {
|
||||
if (!$this->configImporter) {
|
||||
// Set up the ConfigImporter object for testing.
|
||||
$config_comparer = new StorageComparerManifest(
|
||||
$this->container->get('config.storage.staging'),
|
||||
$this->container->get('config.storage'));
|
||||
$this->configImporter = new ConfigImporter(
|
||||
$config_comparer,
|
||||
$this->container->get('event_dispatcher'),
|
||||
$this->container->get('config.factory'),
|
||||
$this->container->get('plugin.manager.entity'),
|
||||
$this->container->get('lock')
|
||||
);
|
||||
}
|
||||
// Always recalculate the changelist when called.
|
||||
return $this->configImporter->reset();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,13 @@ namespace Drupal\system\Tests\Database;
|
|||
*/
|
||||
class RegressionTest extends DatabaseTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('node');
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Regression tests',
|
||||
|
|
|
@ -25,7 +25,7 @@ class TextPlainUnitTest extends DrupalUnitTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('entity', 'field', 'field_sql_storage', 'text', 'field_test');
|
||||
public static $modules = array('system', 'entity', 'field', 'field_sql_storage', 'text', 'field_test');
|
||||
|
||||
/**
|
||||
* Contains rendered content.
|
||||
|
@ -45,7 +45,8 @@ class TextPlainUnitTest extends DrupalUnitTestBase {
|
|||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig(array('field'));
|
||||
// Configure the theme system.
|
||||
$this->installConfig(array('system', 'field'));
|
||||
|
||||
// @todo Add helper methods for all of the following.
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\views\Tests\ViewTestConfigInstaller.
|
||||
*/
|
||||
|
||||
namespace Drupal\views\Tests;
|
||||
|
||||
use Drupal\Core\Config\ConfigImporter;
|
||||
|
||||
/**
|
||||
* Defines a configuration installer.
|
||||
*
|
||||
* A config installer imports test views for views testing.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigImporter
|
||||
* @see \Drupal\views\Tests\ViewTestData
|
||||
*/
|
||||
class ViewTestConfigInstaller extends ConfigImporter {
|
||||
|
||||
/**
|
||||
* The name used to identify events and the lock.
|
||||
*/
|
||||
const ID = 'views.test.installer';
|
||||
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
namespace Drupal\views\Tests;
|
||||
|
||||
use Drupal\Core\Config\FileStorage;
|
||||
use Drupal\Core\Config\StorageComparer;
|
||||
|
||||
/**
|
||||
* Provides tests view data and the base test schema with sample data records.
|
||||
|
@ -39,13 +40,6 @@ class ViewTestData {
|
|||
$class = get_parent_class($class);
|
||||
}
|
||||
if (!empty($views)) {
|
||||
$target_storage = drupal_container()->get('config.storage');
|
||||
$config_changes = array(
|
||||
'delete' => array(),
|
||||
'create' => array(),
|
||||
'change' => array(),
|
||||
);
|
||||
|
||||
$module_handler = \Drupal::moduleHandler();
|
||||
foreach ($modules as $module) {
|
||||
$config_dir = drupal_get_path('module', $module) . '/test_views';
|
||||
|
@ -54,16 +48,27 @@ class ViewTestData {
|
|||
}
|
||||
|
||||
$source_storage = new FileStorage($config_dir);
|
||||
// Only import views used by test.
|
||||
$views_to_import = array();
|
||||
foreach ($source_storage->listAll('views.view.') as $config_name) {
|
||||
$id = str_replace('views.view.', '', $config_name);
|
||||
if (in_array($id, $views)) {
|
||||
$config_changes['create'][] = $config_name;
|
||||
$views_to_import[] = $config_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($config_changes['create'])) {
|
||||
$remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
|
||||
config_sync_changes($remaining_changes, $source_storage, $target_storage);
|
||||
$storage_comparer = new StorageComparer(
|
||||
$source_storage,
|
||||
\Drupal::service('config.storage')
|
||||
);
|
||||
$storage_comparer->addChangelist('create', $views_to_import);
|
||||
$installer = new ViewTestConfigInstaller(
|
||||
$storage_comparer,
|
||||
\Drupal::service('event_dispatcher'),
|
||||
\Drupal::service('config.factory'),
|
||||
\Drupal::entityManager(),
|
||||
\Drupal::lock()
|
||||
);
|
||||
$installer->import();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ abstract class ViewUnitTestBase extends DrupalUnitTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('views', 'views_test_config', 'views_test_data');
|
||||
public static $modules = array('system', 'views', 'views_test_config', 'views_test_data');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
@ -62,6 +62,9 @@ abstract class ViewUnitTestBase extends DrupalUnitTestBase {
|
|||
}
|
||||
$query->execute();
|
||||
|
||||
// Tests implementing ViewUnitTestBase depend on the theme system being
|
||||
// properly configured.
|
||||
$this->installConfig(array('system'));
|
||||
ViewTestData::importTestViews(get_class($this), array('views_test_config'));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue