ConfigImporter.php in Zircon Profile 8.0
Same filename and directory in other branches
Contains \Drupal\Core\Config\ConfigImporter.
Namespace
Drupal\Core\ConfigFile
core/lib/Drupal/Core/Config/ConfigImporter.phpView source
<?php
/**
* @file
* Contains \Drupal\Core\Config\ConfigImporter.
*/
namespace Drupal\Core\Config;
use Drupal\Core\Config\Importer\MissingContentEvent;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Config\Entity\ImportableEntityStorageInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
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:
* - ConfigEvents::IMPORT_VALIDATE: Events listening can throw a
* \Drupal\Core\Config\ConfigImporterException to prevent an import from
* occurring.
* @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
* - ConfigEvents::IMPORT: Events listening can react to a successful import.
* @see \Drupal\Core\EventSubscriber\ConfigSnapshotSubscriber
*
* @see \Drupal\Core\Config\ConfigImporterEvent
*/
class ConfigImporter {
use StringTranslationTrait;
use DependencySerializationTrait;
/**
* The name used to identify the lock.
*/
const LOCK_NAME = '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\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* The configuration manager.
*
* @var \Drupal\Core\Config\ConfigManagerInterface
*/
protected $configManager;
/**
* The used lock backend instance.
*
* @var \Drupal\Core\Lock\LockBackendInterface
*/
protected $lock;
/**
* The typed config manager.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface
*/
protected $typedConfigManager;
/**
* List of configuration file changes processed by the import().
*
* @var array
*/
protected $processedConfiguration;
/**
* List of extension changes processed by the import().
*
* @var array
*/
protected $processedExtensions;
/**
* List of extension changes to be processed by the import().
*
* @var array
*/
protected $extensionChangelist;
/**
* Indicates changes to import have been validated.
*
* @var bool
*/
protected $validated;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* Flag set to import system.theme during processing theme install and uninstalls.
*
* @var bool
*/
protected $processedSystemTheme = FALSE;
/**
* A log of any errors encountered.
*
* If errors are logged during the validation event the configuration
* synchronization will not occur. If errors occur during an import then best
* efforts are made to complete the synchronization.
*
* @var array
*/
protected $errors = array();
/**
* The total number of extensions to process.
*
* @var int
*/
protected $totalExtensionsToProcess = 0;
/**
* The total number of configuration objects to process.
*
* @var int
*/
protected $totalConfigurationToProcess = 0;
/**
* The module installer.
*
* @var \Drupal\Core\Extension\ModuleInstallerInterface
*/
protected $moduleInstaller;
/**
* Constructs a configuration import object.
*
* @param \Drupal\Core\Config\StorageComparerInterface $storage_comparer
* A storage comparer object used to determine 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\ConfigManagerInterface $config_manager
* The configuration manager.
* @param \Drupal\Core\Lock\LockBackendInterface $lock
* The lock backend to ensure multiple imports do not occur at the same time.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
* The typed configuration manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler
* @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
* The module installer.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
*/
public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler, TranslationInterface $string_translation) {
$this->storageComparer = $storage_comparer;
$this->eventDispatcher = $event_dispatcher;
$this->configManager = $config_manager;
$this->lock = $lock;
$this->typedConfigManager = $typed_config;
$this->moduleHandler = $module_handler;
$this->moduleInstaller = $module_installer;
$this->themeHandler = $theme_handler;
$this->stringTranslation = $string_translation;
foreach ($this->storageComparer
->getAllCollectionNames() as $collection) {
$this->processedConfiguration[$collection] = $this->storageComparer
->getEmptyChangelist();
}
$this->processedExtensions = $this
->getEmptyExtensionsProcessedList();
}
/**
* Logs an error message.
*
* @param string $message
* The message to log.
*/
public function logError($message) {
$this->errors[] = $message;
}
/**
* Returns error messages created while running the import.
*
* @return array
* List of messages.
*/
public function getErrors() {
return $this->errors;
}
/**
* 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();
// Empty all the lists.
foreach ($this->storageComparer
->getAllCollectionNames() as $collection) {
$this->processedConfiguration[$collection] = $this->storageComparer
->getEmptyChangelist();
}
$this->extensionChangelist = $this->processedExtensions = $this
->getEmptyExtensionsProcessedList();
$this->validated = FALSE;
$this->processedSystemTheme = FALSE;
return $this;
}
/**
* Gets an empty list of extensions to process.
*
* @return array
* An empty list of extensions to process.
*/
protected function getEmptyExtensionsProcessedList() {
return array(
'module' => array(
'install' => array(),
'uninstall' => array(),
),
'theme' => array(
'install' => array(),
'uninstall' => array(),
),
);
}
/**
* Checks if there are any unprocessed configuration changes.
*
* @return bool
* TRUE if there are changes to process and FALSE if not.
*/
public function hasUnprocessedConfigurationChanges() {
foreach ($this->storageComparer
->getAllCollectionNames() as $collection) {
foreach (array(
'delete',
'create',
'rename',
'update',
) as $op) {
if (count($this
->getUnprocessedConfiguration($op, $collection))) {
return TRUE;
}
}
}
return FALSE;
}
/**
* Gets list of processed changes.
*
* @param string $collection
* (optional) The configuration collection to get processed changes for.
* Defaults to the default collection.
*
* @return array
* An array containing a list of processed changes.
*/
public function getProcessedConfiguration($collection = StorageInterface::DEFAULT_COLLECTION) {
return $this->processedConfiguration[$collection];
}
/**
* Sets a change as processed.
*
* @param string $collection
* The configuration collection to set a change as processed for.
* @param string $op
* The change operation performed, either delete, create, rename, or update.
* @param string $name
* The name of the configuration processed.
*/
protected function setProcessedConfiguration($collection, $op, $name) {
$this->processedConfiguration[$collection][$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, rename, or update.
* @param string $collection
* (optional) The configuration collection to get unprocessed changes for.
* Defaults to the default collection.
*
* @return array
* An array of configuration names.
*/
public function getUnprocessedConfiguration($op, $collection = StorageInterface::DEFAULT_COLLECTION) {
return array_diff($this->storageComparer
->getChangelist($op, $collection), $this->processedConfiguration[$collection][$op]);
}
/**
* Gets list of processed extension changes.
*
* @return array
* An array containing a list of processed extension changes.
*/
public function getProcessedExtensions() {
return $this->processedExtensions;
}
/**
* Sets an extension change as processed.
*
* @param string $type
* The type of extension, either 'theme' or 'module'.
* @param string $op
* The change operation performed, either install or uninstall.
* @param string $name
* The name of the extension processed.
*/
protected function setProcessedExtension($type, $op, $name) {
$this->processedExtensions[$type][$op][] = $name;
}
/**
* Populates the extension change list.
*/
protected function createExtensionChangelist() {
// Create an empty changelist.
$this->extensionChangelist = $this
->getEmptyExtensionsProcessedList();
// Read the extensions information to determine changes.
$current_extensions = $this->storageComparer
->getTargetStorage()
->read('core.extension');
$new_extensions = $this->storageComparer
->getSourceStorage()
->read('core.extension');
// If there is no extension information in sync then exit. This is probably
// due to an empty sync directory.
if (!$new_extensions) {
return;
}
// Get a list of modules with dependency weights as values.
$module_data = system_rebuild_module_data();
// Set the actual module weights.
$module_list = array_combine(array_keys($module_data), array_keys($module_data));
$module_list = array_map(function ($module) use ($module_data) {
return $module_data[$module]->sort;
}, $module_list);
// Determine which modules to uninstall.
$uninstall = array_keys(array_diff_key($current_extensions['module'], $new_extensions['module']));
// Sort the list of newly uninstalled extensions by their weights, so that
// dependencies are uninstalled last. Extensions of the same weight are
// sorted in reverse alphabetical order, to ensure the order is exactly
// opposite from installation. For example, this module list:
// array(
// 'actions' => 0,
// 'ban' => 0,
// 'options' => -2,
// 'text' => -1,
// );
// will result in the following sort order:
// -2 options
// -1 text
// 0 0 ban
// 0 1 actions
// @todo Move this sorting functionality to the extension system.
array_multisort(array_values($module_list), SORT_ASC, array_keys($module_list), SORT_DESC, $module_list);
$this->extensionChangelist['module']['uninstall'] = array_intersect(array_keys($module_list), $uninstall);
// Determine which modules to install.
$install = array_keys(array_diff_key($new_extensions['module'], $current_extensions['module']));
// Ensure that installed modules are sorted in exactly the reverse order
// (with dependencies installed first, and modules of the same weight sorted
// in alphabetical order).
$module_list = array_reverse($module_list);
$this->extensionChangelist['module']['install'] = array_intersect(array_keys($module_list), $install);
// Work out what themes to install and to uninstall.
$this->extensionChangelist['theme']['install'] = array_keys(array_diff_key($new_extensions['theme'], $current_extensions['theme']));
$this->extensionChangelist['theme']['uninstall'] = array_keys(array_diff_key($current_extensions['theme'], $new_extensions['theme']));
}
/**
* Gets a list changes for extensions.
*
* @param string $type
* The type of extension, either 'theme' or 'module'.
* @param string $op
* The change operation to get the unprocessed list for, either install
* or uninstall.
*
* @return array
* An array of extension names.
*/
public function getExtensionChangelist($type, $op = NULL) {
if ($op) {
return $this->extensionChangelist[$type][$op];
}
return $this->extensionChangelist[$type];
}
/**
* Gets a list of unprocessed changes for extensions.
*
* @param string $type
* The type of extension, either 'theme' or 'module'.
*
* @return array
* An array of extension names.
*/
protected function getUnprocessedExtensions($type) {
$changelist = $this
->getExtensionChangelist($type);
return array(
'install' => array_diff($changelist['install'], $this->processedExtensions[$type]['install']),
'uninstall' => array_diff($changelist['uninstall'], $this->processedExtensions[$type]['uninstall']),
);
}
/**
* Imports the changelist to the target storage.
*
* @return \Drupal\Core\Config\ConfigImporter
* The ConfigImporter instance.
*
* @throws \Drupal\Core\Config\ConfigException
*/
public function import() {
if ($this
->hasUnprocessedConfigurationChanges()) {
$sync_steps = $this
->initialize();
foreach ($sync_steps as $step) {
$context = array();
do {
$this
->doSyncStep($step, $context);
} while ($context['finished'] < 1);
}
}
return $this;
}
/**
* Calls a config import step.
*
* @param string|callable $sync_step
* The step to do. Either a method on the ConfigImporter class or a
* callable.
* @param array $context
* A batch context array. If the config importer is not running in a batch
* the only array key that is used is $context['finished']. A process needs
* to set $context['finished'] = 1 when it is done.
*
* @throws \InvalidArgumentException
* Exception thrown if the $sync_step can not be called.
*/
public function doSyncStep($sync_step, &$context) {
if (!is_array($sync_step) && method_exists($this, $sync_step)) {
$this
->{$sync_step}($context);
}
elseif (is_callable($sync_step)) {
call_user_func_array($sync_step, array(
&$context,
$this,
));
}
else {
throw new \InvalidArgumentException('Invalid configuration synchronization step');
}
}
/**
* Initializes the config importer in preparation for processing a batch.
*
* @return array
* An array of \Drupal\Core\Config\ConfigImporter method names and callables
* that are invoked to complete the import. If there are modules or themes
* to process then an extra step is added.
*
* @throws \Drupal\Core\Config\ConfigImporterException
* If the configuration is already importing.
*/
public function initialize() {
// Ensure that the changes have been validated.
$this
->validate();
if (!$this->lock
->acquire(static::LOCK_NAME)) {
// Another process is synchronizing configuration.
throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_NAME));
}
$sync_steps = array();
$modules = $this
->getUnprocessedExtensions('module');
foreach (array(
'install',
'uninstall',
) as $op) {
$this->totalExtensionsToProcess += count($modules[$op]);
}
$themes = $this
->getUnprocessedExtensions('theme');
foreach (array(
'install',
'uninstall',
) as $op) {
$this->totalExtensionsToProcess += count($themes[$op]);
}
// We have extensions to process.
if ($this->totalExtensionsToProcess > 0) {
$sync_steps[] = 'processExtensions';
}
$sync_steps[] = 'processConfigurations';
$sync_steps[] = 'processMissingContent';
// Allow modules to add new steps to configuration synchronization.
$this->moduleHandler
->alter('config_import_steps', $sync_steps, $this);
$sync_steps[] = 'finish';
return $sync_steps;
}
/**
* Processes extensions as a batch operation.
*
* @param array $context.
* The batch context.
*/
protected function processExtensions(array &$context) {
$operation = $this
->getNextExtensionOperation();
if (!empty($operation)) {
$this
->processExtension($operation['type'], $operation['op'], $operation['name']);
$context['message'] = t('Synchronizing extensions: @op @name.', array(
'@op' => $operation['op'],
'@name' => $operation['name'],
));
$processed_count = count($this->processedExtensions['module']['install']) + count($this->processedExtensions['module']['uninstall']);
$processed_count += count($this->processedExtensions['theme']['uninstall']) + count($this->processedExtensions['theme']['install']);
$context['finished'] = $processed_count / $this->totalExtensionsToProcess;
}
else {
$context['finished'] = 1;
}
}
/**
* Processes configuration as a batch operation.
*
* @param array $context.
* The batch context.
*/
protected function processConfigurations(array &$context) {
// The first time this is called we need to calculate the total to process.
// This involves recalculating the changelist which will ensure that if
// extensions have been processed any configuration affected will be taken
// into account.
if ($this->totalConfigurationToProcess == 0) {
$this->storageComparer
->reset();
foreach ($this->storageComparer
->getAllCollectionNames() as $collection) {
foreach (array(
'delete',
'create',
'rename',
'update',
) as $op) {
$this->totalConfigurationToProcess += count($this
->getUnprocessedConfiguration($op, $collection));
}
}
}
$operation = $this
->getNextConfigurationOperation();
if (!empty($operation)) {
if ($this
->checkOp($operation['collection'], $operation['op'], $operation['name'])) {
$this
->processConfiguration($operation['collection'], $operation['op'], $operation['name']);
}
if ($operation['collection'] == StorageInterface::DEFAULT_COLLECTION) {
$context['message'] = $this
->t('Synchronizing configuration: @op @name.', array(
'@op' => $operation['op'],
'@name' => $operation['name'],
));
}
else {
$context['message'] = $this
->t('Synchronizing configuration: @op @name in @collection.', array(
'@op' => $operation['op'],
'@name' => $operation['name'],
'@collection' => $operation['collection'],
));
}
$processed_count = 0;
foreach ($this->storageComparer
->getAllCollectionNames() as $collection) {
foreach (array(
'delete',
'create',
'rename',
'update',
) as $op) {
$processed_count += count($this->processedConfiguration[$collection][$op]);
}
}
$context['finished'] = $processed_count / $this->totalConfigurationToProcess;
}
else {
$context['finished'] = 1;
}
}
/**
* Handles processing of missing content.
*
* @param array $context
* Standard batch context.
*/
protected function processMissingContent(array &$context) {
$sandbox =& $context['sandbox']['config'];
if (!isset($sandbox['missing_content'])) {
$missing_content = $this->configManager
->findMissingContentDependencies();
$sandbox['missing_content']['data'] = $missing_content;
$sandbox['missing_content']['total'] = count($missing_content);
}
else {
$missing_content = $sandbox['missing_content']['data'];
}
if (!empty($missing_content)) {
$event = new MissingContentEvent($missing_content);
// Fire an event to allow listeners to create the missing content.
$this->eventDispatcher
->dispatch(ConfigEvents::IMPORT_MISSING_CONTENT, $event);
$sandbox['missing_content']['data'] = $event
->getMissingContent();
}
$current_count = count($sandbox['missing_content']['data']);
if ($current_count) {
$context['message'] = $this
->t('Resolving missing content');
$context['finished'] = ($sandbox['missing_content']['total'] - $current_count) / $sandbox['missing_content']['total'];
}
else {
$context['finished'] = 1;
}
}
/**
* Finishes the batch.
*
* @param array $context.
* The batch context.
*/
protected function finish(array &$context) {
$this->eventDispatcher
->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
// The import is now complete.
$this->lock
->release(static::LOCK_NAME);
$this
->reset();
$context['message'] = t('Finalizing configuration synchronization.');
$context['finished'] = 1;
}
/**
* Gets the next extension operation to perform.
*
* @return array|bool
* An array containing the next operation and extension name to perform it
* on. If there is nothing left to do returns FALSE;
*/
protected function getNextExtensionOperation() {
foreach (array(
'module',
'theme',
) as $type) {
foreach (array(
'install',
'uninstall',
) as $op) {
$unprocessed = $this
->getUnprocessedExtensions($type);
if (!empty($unprocessed[$op])) {
return array(
'op' => $op,
'type' => $type,
'name' => array_shift($unprocessed[$op]),
);
}
}
}
return FALSE;
}
/**
* Gets the next configuration operation to perform.
*
* @return array|bool
* An array containing the next operation and configuration name to perform
* it on. If there is nothing left to do returns FALSE;
*/
protected function getNextConfigurationOperation() {
// The order configuration operations is processed is important. Deletes
// have to come first so that recreates can work.
foreach ($this->storageComparer
->getAllCollectionNames() as $collection) {
foreach (array(
'delete',
'create',
'rename',
'update',
) as $op) {
$config_names = $this
->getUnprocessedConfiguration($op, $collection);
if (!empty($config_names)) {
return array(
'op' => $op,
'name' => array_shift($config_names),
'collection' => $collection,
);
}
}
}
return FALSE;
}
/**
* Dispatches validate event for a ConfigImporter object.
*
* Events should throw a \Drupal\Core\Config\ConfigImporterException to
* prevent an import from occurring.
*
* @throws \Drupal\Core\Config\ConfigImporterException
* Exception thrown if the validate event logged any errors.
*/
public function validate() {
if (!$this->validated) {
// Create the list of installs and uninstalls.
$this
->createExtensionChangelist();
// Validate renames.
foreach ($this
->getUnprocessedConfiguration('rename') as $name) {
$names = $this->storageComparer
->extractRenameNames($name);
$old_entity_type_id = $this->configManager
->getEntityTypeIdByName($names['old_name']);
$new_entity_type_id = $this->configManager
->getEntityTypeIdByName($names['new_name']);
if ($old_entity_type_id != $new_entity_type_id) {
$this
->logError($this
->t('Entity type mismatch on rename. @old_type not equal to @new_type for existing configuration @old_name and staged configuration @new_name.', array(
'@old_type' => $old_entity_type_id,
'@new_type' => $new_entity_type_id,
'@old_name' => $names['old_name'],
'@new_name' => $names['new_name'],
)));
}
// Has to be a configuration entity.
if (!$old_entity_type_id) {
$this
->logError($this
->t('Rename operation for simple configuration. Existing configuration @old_name and staged configuration @new_name.', array(
'@old_name' => $names['old_name'],
'@new_name' => $names['new_name'],
)));
}
}
$this->eventDispatcher
->dispatch(ConfigEvents::IMPORT_VALIDATE, new ConfigImporterEvent($this));
if (count($this
->getErrors())) {
throw new ConfigImporterException('There were errors validating the config synchronization.');
}
else {
$this->validated = TRUE;
}
}
return $this;
}
/**
* Processes a configuration change.
*
* @param string $collection
* The configuration collection to process changes for.
* @param string $op
* The change operation.
* @param string $name
* The name of the configuration to process.
*
* @throws \Exception
* Thrown when the import process fails, only thrown when no importer log is
* set, otherwise the exception message is logged and the configuration
* is skipped.
*/
protected function processConfiguration($collection, $op, $name) {
try {
$processed = FALSE;
if ($collection == StorageInterface::DEFAULT_COLLECTION) {
$processed = $this
->importInvokeOwner($collection, $op, $name);
}
if (!$processed) {
$this
->importConfig($collection, $op, $name);
}
} catch (\Exception $e) {
$this
->logError($this
->t('Unexpected error during import with operation @op for @name: @message', array(
'@op' => $op,
'@name' => $name,
'@message' => $e
->getMessage(),
)));
// Error for that operation was logged, mark it as processed so that
// the import can continue.
$this
->setProcessedConfiguration($collection, $op, $name);
}
}
/**
* Processes an extension change.
*
* @param string $type
* The type of extension, either 'module' or 'theme'.
* @param string $op
* The change operation.
* @param string $name
* The name of the extension to process.
*/
protected function processExtension($type, $op, $name) {
// Set the config installer to use the sync directory instead of the
// extensions own default config directories.
\Drupal::service('config.installer')
->setSyncing(TRUE)
->setSourceStorage($this->storageComparer
->getSourceStorage());
if ($type == 'module') {
$this->moduleInstaller
->{$op}(array(
$name,
), FALSE);
// Installing a module can cause a kernel boot therefore reinject all the
// services.
$this
->reInjectMe();
// During a module install or uninstall the container is rebuilt and the
// module handler is called from drupal_get_complete_schema(). This causes
// the container's instance of the module handler not to have loaded all
// the enabled modules.
$this->moduleHandler
->loadAll();
}
if ($type == 'theme') {
// Theme uninstalls possible remove default or admin themes therefore we
// need to import this before doing any. If there are no uninstalls and
// the default or admin theme is changing this will be picked up whilst
// processing configuration.
if ($op == 'uninstall' && $this->processedSystemTheme === FALSE) {
$this
->importConfig(StorageInterface::DEFAULT_COLLECTION, 'update', 'system.theme');
$this->configManager
->getConfigFactory()
->reset('system.theme');
$this->processedSystemTheme = TRUE;
}
$this->themeHandler
->{$op}(array(
$name,
));
}
$this
->setProcessedExtension($type, $op, $name);
\Drupal::service('config.installer')
->setSyncing(FALSE);
}
/**
* Checks that the operation is still valid.
*
* During a configuration import secondary writes and deletes are possible.
* This method checks that the operation is still valid before processing a
* configuration change.
*
* @param string $collection
* The configuration collection.
* @param string $op
* The change operation.
* @param string $name
* The name of the configuration to process.
*
* @return bool
* TRUE is to continue processing, FALSE otherwise.
*
* @throws \Drupal\Core\Config\ConfigImporterException
*/
protected function checkOp($collection, $op, $name) {
if ($op == 'rename') {
$names = $this->storageComparer
->extractRenameNames($name);
$target_exists = $this->storageComparer
->getTargetStorage($collection)
->exists($names['new_name']);
if ($target_exists) {
// If the target exists, the rename has already occurred as the
// result of a secondary configuration write. Change the operation
// into an update. This is the desired behavior since renames often
// have to occur together. For example, renaming a node type must
// also result in renaming its fields and entity displays.
$this->storageComparer
->moveRenameToUpdate($name);
return FALSE;
}
return TRUE;
}
$target_exists = $this->storageComparer
->getTargetStorage($collection)
->exists($name);
switch ($op) {
case 'delete':
if (!$target_exists) {
// The configuration has already been deleted. For example, a field
// is automatically deleted if all the instances are.
$this
->setProcessedConfiguration($collection, $op, $name);
return FALSE;
}
break;
case 'create':
if ($target_exists) {
// If the target already exists, use the entity storage to delete it
// again, if is a simple config, delete it directly.
if ($entity_type_id = $this->configManager
->getEntityTypeIdByName($name)) {
$entity_storage = $this->configManager
->getEntityManager()
->getStorage($entity_type_id);
$entity_type = $this->configManager
->getEntityManager()
->getDefinition($entity_type_id);
$entity = $entity_storage
->load($entity_storage
->getIDFromConfigName($name, $entity_type
->getConfigPrefix()));
$entity
->delete();
$this
->logError($this
->t('Deleted and replaced configuration entity "@name"', array(
'@name' => $name,
)));
}
else {
$this->storageComparer
->getTargetStorage($collection)
->delete($name);
$this
->logError($this
->t('Deleted and replaced configuration "@name"', array(
'@name' => $name,
)));
}
return TRUE;
}
break;
case 'update':
if (!$target_exists) {
$this
->logError($this
->t('Update target "@name" is missing.', array(
'@name' => $name,
)));
// Mark as processed so that the synchronization continues. Once the
// the current synchronization is complete it will show up as a
// create.
$this
->setProcessedConfiguration($collection, $op, $name);
return FALSE;
}
break;
}
return TRUE;
}
/**
* Writes a configuration change from the source to the target storage.
*
* @param string $collection
* The configuration collection.
* @param string $op
* The change operation.
* @param string $name
* The name of the configuration to process.
*/
protected function importConfig($collection, $op, $name) {
// Allow config factory overriders to use a custom configuration object if
// they are responsible for the collection.
$overrider = $this->configManager
->getConfigCollectionInfo()
->getOverrideService($collection);
if ($overrider) {
$config = $overrider
->createConfigObject($name, $collection);
}
else {
$config = new Config($name, $this->storageComparer
->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
}
if ($op == 'delete') {
$config
->delete();
}
else {
$data = $this->storageComparer
->getSourceStorage($collection)
->read($name);
$config
->setData($data ? $data : array());
$config
->save();
}
$this
->setProcessedConfiguration($collection, $op, $name);
}
/**
* Invokes import* methods on configuration entity storage.
*
* Allow modules to take over configuration change operations for higher-level
* configuration data.
*
* @todo Add support for other extension types; e.g., themes etc.
*
* @param string $collection
* The configuration collection.
* @param string $op
* The change operation to get the unprocessed list for, either delete,
* create, rename, or update.
* @param string $name
* The name of the configuration to process.
*
* @return bool
* TRUE if the configuration was imported as a configuration entity. FALSE
* otherwise.
*
* @throws \Drupal\Core\Entity\EntityStorageException
* Thrown if the data is owned by an entity type, but the entity storage
* does not support imports.
*/
protected function importInvokeOwner($collection, $op, $name) {
// Renames are handled separately.
if ($op == 'rename') {
return $this
->importInvokeRename($collection, $name);
}
// Validate the configuration object name before importing it.
// Config::validateName($name);
if ($entity_type = $this->configManager
->getEntityTypeIdByName($name)) {
$old_config = new Config($name, $this->storageComparer
->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
if ($old_data = $this->storageComparer
->getTargetStorage($collection)
->read($name)) {
$old_config
->initWithData($old_data);
}
$data = $this->storageComparer
->getSourceStorage($collection)
->read($name);
$new_config = new Config($name, $this->storageComparer
->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
if ($data !== FALSE) {
$new_config
->setData($data);
}
$method = 'import' . ucfirst($op);
$entity_storage = $this->configManager
->getEntityManager()
->getStorage($entity_type);
// Call to the configuration entity's storage to handle the configuration
// change.
if (!$entity_storage instanceof ImportableEntityStorageInterface) {
throw new EntityStorageException(sprintf('The entity storage "%s" for the "%s" entity type does not support imports', get_class($entity_storage), $entity_type));
}
$entity_storage
->{$method}($name, $new_config, $old_config);
$this
->setProcessedConfiguration($collection, $op, $name);
return TRUE;
}
return FALSE;
}
/**
* Imports a configuration entity rename.
*
* @param string $collection
* The configuration collection.
* @param string $rename_name
* The rename configuration name, as provided by
* \Drupal\Core\Config\StorageComparer::createRenameName().
*
* @return bool
* TRUE if the configuration was imported as a configuration entity. FALSE
* otherwise.
*
* @throws \Drupal\Core\Entity\EntityStorageException
* Thrown if the data is owned by an entity type, but the entity storage
* does not support imports.
*
* @see \Drupal\Core\Config\ConfigImporter::createRenameName()
*/
protected function importInvokeRename($collection, $rename_name) {
$names = $this->storageComparer
->extractRenameNames($rename_name);
$entity_type_id = $this->configManager
->getEntityTypeIdByName($names['old_name']);
$old_config = new Config($names['old_name'], $this->storageComparer
->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
if ($old_data = $this->storageComparer
->getTargetStorage($collection)
->read($names['old_name'])) {
$old_config
->initWithData($old_data);
}
$data = $this->storageComparer
->getSourceStorage($collection)
->read($names['new_name']);
$new_config = new Config($names['new_name'], $this->storageComparer
->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
if ($data !== FALSE) {
$new_config
->setData($data);
}
$entity_storage = $this->configManager
->getEntityManager()
->getStorage($entity_type_id);
// Call to the configuration entity's storage to handle the configuration
// change.
if (!$entity_storage instanceof ImportableEntityStorageInterface) {
throw new EntityStorageException(sprintf("The entity storage '%s' for the '%s' entity type does not support imports", get_class($entity_storage), $entity_type_id));
}
$entity_storage
->importRename($names['old_name'], $new_config, $old_config);
$this
->setProcessedConfiguration($collection, 'rename', $rename_name);
return TRUE;
}
/**
* 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::LOCK_NAME);
}
/**
* Gets all the service dependencies from \Drupal.
*
* Since the ConfigImporter handles module installation the kernel and the
* container can be rebuilt and altered during processing. It is necessary to
* keep the services used by the importer in sync.
*/
protected function reInjectMe() {
$this->_serviceIds = array();
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
if (is_object($value) && isset($value->_serviceId)) {
$this->{$key} = \Drupal::service($value->_serviceId);
}
}
}
}
Classes
Name | Description |
---|---|
ConfigImporter | Defines a configuration importer. |