3 namespace Drupal\Core\Config;
5 use Drupal\Core\Config\Importer\MissingContentEvent;
6 use Drupal\Core\Extension\ModuleHandlerInterface;
7 use Drupal\Core\Extension\ModuleInstallerInterface;
8 use Drupal\Core\Extension\ThemeHandlerInterface;
9 use Drupal\Core\Config\Entity\ImportableEntityStorageInterface;
10 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
11 use Drupal\Core\Entity\EntityStorageException;
12 use Drupal\Core\Lock\LockBackendInterface;
13 use Drupal\Core\StringTranslation\StringTranslationTrait;
14 use Drupal\Core\StringTranslation\TranslationInterface;
15 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
18 * Defines a configuration importer.
20 * A config importer imports the changes into the configuration system. To
21 * determine which changes to import a StorageComparer in used.
23 * @see \Drupal\Core\Config\StorageComparerInterface
25 * The ConfigImporter has a identifier which is used to construct event names.
26 * The events fired during an import are:
27 * - ConfigEvents::IMPORT_VALIDATE: Events listening can throw a
28 * \Drupal\Core\Config\ConfigImporterException to prevent an import from
30 * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
31 * - ConfigEvents::IMPORT: Events listening can react to a successful import.
32 * @see \Drupal\Core\EventSubscriber\ConfigSnapshotSubscriber
34 * @see \Drupal\Core\Config\ConfigImporterEvent
36 class ConfigImporter {
37 use StringTranslationTrait;
38 use DependencySerializationTrait;
41 * The name used to identify the lock.
43 const LOCK_NAME = 'config_importer';
46 * The storage comparer used to discover configuration changes.
48 * @var \Drupal\Core\Config\StorageComparerInterface
50 protected $storageComparer;
53 * The event dispatcher used to notify subscribers.
55 * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
57 protected $eventDispatcher;
60 * The configuration manager.
62 * @var \Drupal\Core\Config\ConfigManagerInterface
64 protected $configManager;
67 * The used lock backend instance.
69 * @var \Drupal\Core\Lock\LockBackendInterface
74 * The typed config manager.
76 * @var \Drupal\Core\Config\TypedConfigManagerInterface
78 protected $typedConfigManager;
81 * List of configuration file changes processed by the import().
85 protected $processedConfiguration;
88 * List of extension changes processed by the import().
92 protected $processedExtensions;
95 * List of extension changes to be processed by the import().
99 protected $extensionChangelist;
102 * Indicates changes to import have been validated.
106 protected $validated;
109 * The module handler.
111 * @var \Drupal\Core\Extension\ModuleHandlerInterface
113 protected $moduleHandler;
118 * @var \Drupal\Core\Extension\ThemeHandlerInterface
120 protected $themeHandler;
123 * Flag set to import system.theme during processing theme install and uninstalls.
127 protected $processedSystemTheme = FALSE;
130 * A log of any errors encountered.
132 * If errors are logged during the validation event the configuration
133 * synchronization will not occur. If errors occur during an import then best
134 * efforts are made to complete the synchronization.
138 protected $errors = [];
141 * The total number of extensions to process.
145 protected $totalExtensionsToProcess = 0;
148 * The total number of configuration objects to process.
152 protected $totalConfigurationToProcess = 0;
155 * The module installer.
157 * @var \Drupal\Core\Extension\ModuleInstallerInterface
159 protected $moduleInstaller;
162 * Constructs a configuration import object.
164 * @param \Drupal\Core\Config\StorageComparerInterface $storage_comparer
165 * A storage comparer object used to determine configuration changes and
166 * access the source and target storage objects.
167 * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
168 * The event dispatcher used to notify subscribers of config import events.
169 * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
170 * The configuration manager.
171 * @param \Drupal\Core\Lock\LockBackendInterface $lock
172 * The lock backend to ensure multiple imports do not occur at the same time.
173 * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
174 * The typed configuration manager.
175 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
177 * @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
178 * The module installer.
179 * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
181 * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
182 * The string translation service.
184 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) {
185 $this->storageComparer = $storage_comparer;
186 $this->eventDispatcher = $event_dispatcher;
187 $this->configManager = $config_manager;
189 $this->typedConfigManager = $typed_config;
190 $this->moduleHandler = $module_handler;
191 $this->moduleInstaller = $module_installer;
192 $this->themeHandler = $theme_handler;
193 $this->stringTranslation = $string_translation;
194 foreach ($this->storageComparer->getAllCollectionNames() as $collection) {
195 $this->processedConfiguration[$collection] = $this->storageComparer->getEmptyChangelist();
197 $this->processedExtensions = $this->getEmptyExtensionsProcessedList();
201 * Logs an error message.
203 * @param string $message
204 * The message to log.
206 public function logError($message) {
207 $this->errors[] = $message;
211 * Returns error messages created while running the import.
216 public function getErrors() {
217 return $this->errors;
221 * Gets the configuration storage comparer.
223 * @return \Drupal\Core\Config\StorageComparerInterface
224 * Storage comparer object used to calculate configuration changes.
226 public function getStorageComparer() {
227 return $this->storageComparer;
231 * Resets the storage comparer and processed list.
233 * @return \Drupal\Core\Config\ConfigImporter
234 * The ConfigImporter instance.
236 public function reset() {
237 $this->storageComparer->reset();
238 // Empty all the lists.
239 foreach ($this->storageComparer->getAllCollectionNames() as $collection) {
240 $this->processedConfiguration[$collection] = $this->storageComparer->getEmptyChangelist();
242 $this->extensionChangelist = $this->processedExtensions = $this->getEmptyExtensionsProcessedList();
244 $this->validated = FALSE;
245 $this->processedSystemTheme = FALSE;
250 * Gets an empty list of extensions to process.
253 * An empty list of extensions to process.
255 protected function getEmptyExtensionsProcessedList() {
269 * Checks if there are any unprocessed configuration changes.
272 * TRUE if there are changes to process and FALSE if not.
274 public function hasUnprocessedConfigurationChanges() {
275 foreach ($this->storageComparer->getAllCollectionNames() as $collection) {
276 foreach (['delete', 'create', 'rename', 'update'] as $op) {
277 if (count($this->getUnprocessedConfiguration($op, $collection))) {
286 * Gets list of processed changes.
288 * @param string $collection
289 * (optional) The configuration collection to get processed changes for.
290 * Defaults to the default collection.
293 * An array containing a list of processed changes.
295 public function getProcessedConfiguration($collection = StorageInterface::DEFAULT_COLLECTION) {
296 return $this->processedConfiguration[$collection];
300 * Sets a change as processed.
302 * @param string $collection
303 * The configuration collection to set a change as processed for.
305 * The change operation performed, either delete, create, rename, or update.
306 * @param string $name
307 * The name of the configuration processed.
309 protected function setProcessedConfiguration($collection, $op, $name) {
310 $this->processedConfiguration[$collection][$op][] = $name;
314 * Gets a list of unprocessed changes for a given operation.
317 * The change operation to get the unprocessed list for, either delete,
318 * create, rename, or update.
319 * @param string $collection
320 * (optional) The configuration collection to get unprocessed changes for.
321 * Defaults to the default collection.
324 * An array of configuration names.
326 public function getUnprocessedConfiguration($op, $collection = StorageInterface::DEFAULT_COLLECTION) {
327 return array_diff($this->storageComparer->getChangelist($op, $collection), $this->processedConfiguration[$collection][$op]);
331 * Gets list of processed extension changes.
334 * An array containing a list of processed extension changes.
336 public function getProcessedExtensions() {
337 return $this->processedExtensions;
341 * Sets an extension change as processed.
343 * @param string $type
344 * The type of extension, either 'theme' or 'module'.
346 * The change operation performed, either install or uninstall.
347 * @param string $name
348 * The name of the extension processed.
350 protected function setProcessedExtension($type, $op, $name) {
351 $this->processedExtensions[$type][$op][] = $name;
355 * Populates the extension change list.
357 protected function createExtensionChangelist() {
358 // Create an empty changelist.
359 $this->extensionChangelist = $this->getEmptyExtensionsProcessedList();
361 // Read the extensions information to determine changes.
362 $current_extensions = $this->storageComparer->getTargetStorage()->read('core.extension');
363 $new_extensions = $this->storageComparer->getSourceStorage()->read('core.extension');
365 // If there is no extension information in sync then exit. This is probably
366 // due to an empty sync directory.
367 if (!$new_extensions) {
371 // Get a list of modules with dependency weights as values.
372 $module_data = system_rebuild_module_data();
373 // Set the actual module weights.
374 $module_list = array_combine(array_keys($module_data), array_keys($module_data));
375 $module_list = array_map(function ($module) use ($module_data) {
376 return $module_data[$module]->sort;
379 // Determine which modules to uninstall.
380 $uninstall = array_keys(array_diff_key($current_extensions['module'], $new_extensions['module']));
381 // Sort the list of newly uninstalled extensions by their weights, so that
382 // dependencies are uninstalled last. Extensions of the same weight are
383 // sorted in reverse alphabetical order, to ensure the order is exactly
384 // opposite from installation. For example, this module list:
391 // will result in the following sort order:
396 // @todo Move this sorting functionality to the extension system.
397 array_multisort(array_values($module_list), SORT_ASC, array_keys($module_list), SORT_DESC, $module_list);
398 $this->extensionChangelist['module']['uninstall'] = array_intersect(array_keys($module_list), $uninstall);
400 // Determine which modules to install.
401 $install = array_keys(array_diff_key($new_extensions['module'], $current_extensions['module']));
402 // Ensure that installed modules are sorted in exactly the reverse order
403 // (with dependencies installed first, and modules of the same weight sorted
404 // in alphabetical order).
405 $module_list = array_reverse($module_list);
406 $this->extensionChangelist['module']['install'] = array_intersect(array_keys($module_list), $install);
408 // Work out what themes to install and to uninstall.
409 $this->extensionChangelist['theme']['install'] = array_keys(array_diff_key($new_extensions['theme'], $current_extensions['theme']));
410 $this->extensionChangelist['theme']['uninstall'] = array_keys(array_diff_key($current_extensions['theme'], $new_extensions['theme']));
414 * Gets a list changes for extensions.
416 * @param string $type
417 * The type of extension, either 'theme' or 'module'.
419 * The change operation to get the unprocessed list for, either install
423 * An array of extension names.
425 public function getExtensionChangelist($type, $op = NULL) {
427 return $this->extensionChangelist[$type][$op];
429 return $this->extensionChangelist[$type];
433 * Gets a list of unprocessed changes for extensions.
435 * @param string $type
436 * The type of extension, either 'theme' or 'module'.
439 * An array of extension names.
441 protected function getUnprocessedExtensions($type) {
442 $changelist = $this->getExtensionChangelist($type);
444 'install' => array_diff($changelist['install'], $this->processedExtensions[$type]['install']),
445 'uninstall' => array_diff($changelist['uninstall'], $this->processedExtensions[$type]['uninstall']),
450 * Imports the changelist to the target storage.
452 * @return \Drupal\Core\Config\ConfigImporter
453 * The ConfigImporter instance.
455 * @throws \Drupal\Core\Config\ConfigException
457 public function import() {
458 if ($this->hasUnprocessedConfigurationChanges()) {
459 $sync_steps = $this->initialize();
461 foreach ($sync_steps as $step) {
464 $this->doSyncStep($step, $context);
465 } while ($context['finished'] < 1);
472 * Calls a config import step.
474 * @param string|callable $sync_step
475 * The step to do. Either a method on the ConfigImporter class or a
477 * @param array $context
478 * A batch context array. If the config importer is not running in a batch
479 * the only array key that is used is $context['finished']. A process needs
480 * to set $context['finished'] = 1 when it is done.
482 * @throws \InvalidArgumentException
483 * Exception thrown if the $sync_step can not be called.
485 public function doSyncStep($sync_step, &$context) {
486 if (!is_array($sync_step) && method_exists($this, $sync_step)) {
487 \Drupal::service('config.installer')->setSyncing(TRUE);
488 $this->$sync_step($context);
490 elseif (is_callable($sync_step)) {
491 \Drupal::service('config.installer')->setSyncing(TRUE);
492 call_user_func_array($sync_step, [&$context, $this]);
495 throw new \InvalidArgumentException('Invalid configuration synchronization step');
497 \Drupal::service('config.installer')->setSyncing(FALSE);
501 * Initializes the config importer in preparation for processing a batch.
504 * An array of \Drupal\Core\Config\ConfigImporter method names and callables
505 * that are invoked to complete the import. If there are modules or themes
506 * to process then an extra step is added.
508 * @throws \Drupal\Core\Config\ConfigImporterException
509 * If the configuration is already importing.
511 public function initialize() {
512 // Ensure that the changes have been validated.
515 if (!$this->lock->acquire(static::LOCK_NAME)) {
516 // Another process is synchronizing configuration.
517 throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_NAME));
521 $modules = $this->getUnprocessedExtensions('module');
522 foreach (['install', 'uninstall'] as $op) {
523 $this->totalExtensionsToProcess += count($modules[$op]);
525 $themes = $this->getUnprocessedExtensions('theme');
526 foreach (['install', 'uninstall'] as $op) {
527 $this->totalExtensionsToProcess += count($themes[$op]);
530 // We have extensions to process.
531 if ($this->totalExtensionsToProcess > 0) {
532 $sync_steps[] = 'processExtensions';
534 $sync_steps[] = 'processConfigurations';
535 $sync_steps[] = 'processMissingContent';
536 // Allow modules to add new steps to configuration synchronization.
537 $this->moduleHandler->alter('config_import_steps', $sync_steps, $this);
538 $sync_steps[] = 'finish';
543 * Processes extensions as a batch operation.
545 * @param array|\ArrayAccess $context
548 protected function processExtensions(&$context) {
549 $operation = $this->getNextExtensionOperation();
550 if (!empty($operation)) {
551 $this->processExtension($operation['type'], $operation['op'], $operation['name']);
552 $context['message'] = t('Synchronizing extensions: @op @name.', ['@op' => $operation['op'], '@name' => $operation['name']]);
553 $processed_count = count($this->processedExtensions['module']['install']) + count($this->processedExtensions['module']['uninstall']);
554 $processed_count += count($this->processedExtensions['theme']['uninstall']) + count($this->processedExtensions['theme']['install']);
555 $context['finished'] = $processed_count / $this->totalExtensionsToProcess;
558 $context['finished'] = 1;
563 * Processes configuration as a batch operation.
565 * @param array|\ArrayAccess $context
568 protected function processConfigurations(&$context) {
569 // The first time this is called we need to calculate the total to process.
570 // This involves recalculating the changelist which will ensure that if
571 // extensions have been processed any configuration affected will be taken
573 if ($this->totalConfigurationToProcess == 0) {
574 $this->storageComparer->reset();
575 foreach ($this->storageComparer->getAllCollectionNames() as $collection) {
576 foreach (['delete', 'create', 'rename', 'update'] as $op) {
577 $this->totalConfigurationToProcess += count($this->getUnprocessedConfiguration($op, $collection));
581 $operation = $this->getNextConfigurationOperation();
582 if (!empty($operation)) {
583 if ($this->checkOp($operation['collection'], $operation['op'], $operation['name'])) {
584 $this->processConfiguration($operation['collection'], $operation['op'], $operation['name']);
586 if ($operation['collection'] == StorageInterface::DEFAULT_COLLECTION) {
587 $context['message'] = $this->t('Synchronizing configuration: @op @name.', ['@op' => $operation['op'], '@name' => $operation['name']]);
590 $context['message'] = $this->t('Synchronizing configuration: @op @name in @collection.', ['@op' => $operation['op'], '@name' => $operation['name'], '@collection' => $operation['collection']]);
592 $processed_count = 0;
593 foreach ($this->storageComparer->getAllCollectionNames() as $collection) {
594 foreach (['delete', 'create', 'rename', 'update'] as $op) {
595 $processed_count += count($this->processedConfiguration[$collection][$op]);
598 $context['finished'] = $processed_count / $this->totalConfigurationToProcess;
601 $context['finished'] = 1;
606 * Handles processing of missing content.
608 * @param array|\ArrayAccess $context
609 * Standard batch context.
611 protected function processMissingContent(&$context) {
612 $sandbox = &$context['sandbox']['config'];
613 if (!isset($sandbox['missing_content'])) {
614 $missing_content = $this->configManager->findMissingContentDependencies();
615 $sandbox['missing_content']['data'] = $missing_content;
616 $sandbox['missing_content']['total'] = count($missing_content);
619 $missing_content = $sandbox['missing_content']['data'];
621 if (!empty($missing_content)) {
622 $event = new MissingContentEvent($missing_content);
623 // Fire an event to allow listeners to create the missing content.
624 $this->eventDispatcher->dispatch(ConfigEvents::IMPORT_MISSING_CONTENT, $event);
625 $sandbox['missing_content']['data'] = $event->getMissingContent();
627 $current_count = count($sandbox['missing_content']['data']);
628 if ($current_count) {
629 $context['message'] = $this->t('Resolving missing content');
630 $context['finished'] = ($sandbox['missing_content']['total'] - $current_count) / $sandbox['missing_content']['total'];
633 $context['finished'] = 1;
638 * Finishes the batch.
640 * @param array|\ArrayAccess $context
643 protected function finish(&$context) {
644 $this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
645 // The import is now complete.
646 $this->lock->release(static::LOCK_NAME);
648 $context['message'] = t('Finalizing configuration synchronization.');
649 $context['finished'] = 1;
653 * Gets the next extension operation to perform.
656 * An array containing the next operation and extension name to perform it
657 * on. If there is nothing left to do returns FALSE;
659 protected function getNextExtensionOperation() {
660 foreach (['module', 'theme'] as $type) {
661 foreach (['install', 'uninstall'] as $op) {
662 $unprocessed = $this->getUnprocessedExtensions($type);
663 if (!empty($unprocessed[$op])) {
667 'name' => array_shift($unprocessed[$op]),
676 * Gets the next configuration operation to perform.
679 * An array containing the next operation and configuration name to perform
680 * it on. If there is nothing left to do returns FALSE;
682 protected function getNextConfigurationOperation() {
683 // The order configuration operations is processed is important. Deletes
684 // have to come first so that recreates can work.
685 foreach ($this->storageComparer->getAllCollectionNames() as $collection) {
686 foreach (['delete', 'create', 'rename', 'update'] as $op) {
687 $config_names = $this->getUnprocessedConfiguration($op, $collection);
688 if (!empty($config_names)) {
691 'name' => array_shift($config_names),
692 'collection' => $collection,
701 * Dispatches validate event for a ConfigImporter object.
703 * Events should throw a \Drupal\Core\Config\ConfigImporterException to
704 * prevent an import from occurring.
706 * @throws \Drupal\Core\Config\ConfigImporterException
707 * Exception thrown if the validate event logged any errors.
709 public function validate() {
710 if (!$this->validated) {
711 // Create the list of installs and uninstalls.
712 $this->createExtensionChangelist();
714 foreach ($this->getUnprocessedConfiguration('rename') as $name) {
715 $names = $this->storageComparer->extractRenameNames($name);
716 $old_entity_type_id = $this->configManager->getEntityTypeIdByName($names['old_name']);
717 $new_entity_type_id = $this->configManager->getEntityTypeIdByName($names['new_name']);
718 if ($old_entity_type_id != $new_entity_type_id) {
719 $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.', ['@old_type' => $old_entity_type_id, '@new_type' => $new_entity_type_id, '@old_name' => $names['old_name'], '@new_name' => $names['new_name']]));
721 // Has to be a configuration entity.
722 if (!$old_entity_type_id) {
723 $this->logError($this->t('Rename operation for simple configuration. Existing configuration @old_name and staged configuration @new_name.', ['@old_name' => $names['old_name'], '@new_name' => $names['new_name']]));
726 $this->eventDispatcher->dispatch(ConfigEvents::IMPORT_VALIDATE, new ConfigImporterEvent($this));
727 if (count($this->getErrors())) {
728 throw new ConfigImporterException('There were errors validating the config synchronization.');
731 $this->validated = TRUE;
738 * Processes a configuration change.
740 * @param string $collection
741 * The configuration collection to process changes for.
743 * The change operation.
744 * @param string $name
745 * The name of the configuration to process.
748 * Thrown when the import process fails, only thrown when no importer log is
749 * set, otherwise the exception message is logged and the configuration
752 protected function processConfiguration($collection, $op, $name) {
755 if ($collection == StorageInterface::DEFAULT_COLLECTION) {
756 $processed = $this->importInvokeOwner($collection, $op, $name);
759 $this->importConfig($collection, $op, $name);
762 catch (\Exception $e) {
763 $this->logError($this->t('Unexpected error during import with operation @op for @name: @message', ['@op' => $op, '@name' => $name, '@message' => $e->getMessage()]));
764 // Error for that operation was logged, mark it as processed so that
765 // the import can continue.
766 $this->setProcessedConfiguration($collection, $op, $name);
771 * Processes an extension change.
773 * @param string $type
774 * The type of extension, either 'module' or 'theme'.
776 * The change operation.
777 * @param string $name
778 * The name of the extension to process.
780 protected function processExtension($type, $op, $name) {
781 // Set the config installer to use the sync directory instead of the
782 // extensions own default config directories.
783 \Drupal::service('config.installer')
784 ->setSourceStorage($this->storageComparer->getSourceStorage());
785 if ($type == 'module') {
786 $this->moduleInstaller->$op([$name], FALSE);
787 // Installing a module can cause a kernel boot therefore reinject all the
790 // During a module install or uninstall the container is rebuilt and the
791 // module handler is called from drupal_get_complete_schema(). This causes
792 // the container's instance of the module handler not to have loaded all
793 // the enabled modules.
794 $this->moduleHandler->loadAll();
796 if ($type == 'theme') {
797 // Theme uninstalls possible remove default or admin themes therefore we
798 // need to import this before doing any. If there are no uninstalls and
799 // the default or admin theme is changing this will be picked up whilst
800 // processing configuration.
801 if ($op == 'uninstall' && $this->processedSystemTheme === FALSE) {
802 $this->importConfig(StorageInterface::DEFAULT_COLLECTION, 'update', 'system.theme');
803 $this->configManager->getConfigFactory()->reset('system.theme');
804 $this->processedSystemTheme = TRUE;
806 $this->themeHandler->$op([$name]);
809 $this->setProcessedExtension($type, $op, $name);
813 * Checks that the operation is still valid.
815 * During a configuration import secondary writes and deletes are possible.
816 * This method checks that the operation is still valid before processing a
817 * configuration change.
819 * @param string $collection
820 * The configuration collection.
822 * The change operation.
823 * @param string $name
824 * The name of the configuration to process.
827 * TRUE is to continue processing, FALSE otherwise.
829 * @throws \Drupal\Core\Config\ConfigImporterException
831 protected function checkOp($collection, $op, $name) {
832 if ($op == 'rename') {
833 $names = $this->storageComparer->extractRenameNames($name);
834 $target_exists = $this->storageComparer->getTargetStorage($collection)->exists($names['new_name']);
835 if ($target_exists) {
836 // If the target exists, the rename has already occurred as the
837 // result of a secondary configuration write. Change the operation
838 // into an update. This is the desired behavior since renames often
839 // have to occur together. For example, renaming a node type must
840 // also result in renaming its fields and entity displays.
841 $this->storageComparer->moveRenameToUpdate($name);
846 $target_exists = $this->storageComparer->getTargetStorage($collection)->exists($name);
849 if (!$target_exists) {
850 // The configuration has already been deleted. For example, a field
851 // is automatically deleted if all the instances are.
852 $this->setProcessedConfiguration($collection, $op, $name);
858 if ($target_exists) {
859 // If the target already exists, use the entity storage to delete it
860 // again, if is a simple config, delete it directly.
861 if ($entity_type_id = $this->configManager->getEntityTypeIdByName($name)) {
862 $entity_storage = $this->configManager->getEntityManager()->getStorage($entity_type_id);
863 $entity_type = $this->configManager->getEntityManager()->getDefinition($entity_type_id);
864 $entity = $entity_storage->load($entity_storage->getIDFromConfigName($name, $entity_type->getConfigPrefix()));
866 $this->logError($this->t('Deleted and replaced configuration entity "@name"', ['@name' => $name]));
869 $this->storageComparer->getTargetStorage($collection)->delete($name);
870 $this->logError($this->t('Deleted and replaced configuration "@name"', ['@name' => $name]));
877 if (!$target_exists) {
878 $this->logError($this->t('Update target "@name" is missing.', ['@name' => $name]));
879 // Mark as processed so that the synchronization continues. Once the
880 // the current synchronization is complete it will show up as a
882 $this->setProcessedConfiguration($collection, $op, $name);
891 * Writes a configuration change from the source to the target storage.
893 * @param string $collection
894 * The configuration collection.
896 * The change operation.
897 * @param string $name
898 * The name of the configuration to process.
900 protected function importConfig($collection, $op, $name) {
901 // Allow config factory overriders to use a custom configuration object if
902 // they are responsible for the collection.
903 $overrider = $this->configManager->getConfigCollectionInfo()->getOverrideService($collection);
905 $config = $overrider->createConfigObject($name, $collection);
908 $config = new Config($name, $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
910 if ($op == 'delete') {
914 $data = $this->storageComparer->getSourceStorage($collection)->read($name);
915 $config->setData($data ? $data : []);
918 $this->setProcessedConfiguration($collection, $op, $name);
922 * Invokes import* methods on configuration entity storage.
924 * Allow modules to take over configuration change operations for higher-level
925 * configuration data.
927 * @todo Add support for other extension types; e.g., themes etc.
929 * @param string $collection
930 * The configuration collection.
932 * The change operation to get the unprocessed list for, either delete,
933 * create, rename, or update.
934 * @param string $name
935 * The name of the configuration to process.
938 * TRUE if the configuration was imported as a configuration entity. FALSE
941 * @throws \Drupal\Core\Entity\EntityStorageException
942 * Thrown if the data is owned by an entity type, but the entity storage
943 * does not support imports.
945 protected function importInvokeOwner($collection, $op, $name) {
946 // Renames are handled separately.
947 if ($op == 'rename') {
948 return $this->importInvokeRename($collection, $name);
950 // Validate the configuration object name before importing it.
951 // Config::validateName($name);
952 if ($entity_type = $this->configManager->getEntityTypeIdByName($name)) {
953 $old_config = new Config($name, $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
954 if ($old_data = $this->storageComparer->getTargetStorage($collection)->read($name)) {
955 $old_config->initWithData($old_data);
958 $data = $this->storageComparer->getSourceStorage($collection)->read($name);
959 $new_config = new Config($name, $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
960 if ($data !== FALSE) {
961 $new_config->setData($data);
964 $method = 'import' . ucfirst($op);
965 $entity_storage = $this->configManager->getEntityManager()->getStorage($entity_type);
966 // Call to the configuration entity's storage to handle the configuration
968 if (!($entity_storage instanceof ImportableEntityStorageInterface)) {
969 throw new EntityStorageException(sprintf('The entity storage "%s" for the "%s" entity type does not support imports', get_class($entity_storage), $entity_type));
971 $entity_storage->$method($name, $new_config, $old_config);
972 $this->setProcessedConfiguration($collection, $op, $name);
979 * Imports a configuration entity rename.
981 * @param string $collection
982 * The configuration collection.
983 * @param string $rename_name
984 * The rename configuration name, as provided by
985 * \Drupal\Core\Config\StorageComparer::createRenameName().
988 * TRUE if the configuration was imported as a configuration entity. FALSE
991 * @throws \Drupal\Core\Entity\EntityStorageException
992 * Thrown if the data is owned by an entity type, but the entity storage
993 * does not support imports.
995 * @see \Drupal\Core\Config\ConfigImporter::createRenameName()
997 protected function importInvokeRename($collection, $rename_name) {
998 $names = $this->storageComparer->extractRenameNames($rename_name);
999 $entity_type_id = $this->configManager->getEntityTypeIdByName($names['old_name']);
1000 $old_config = new Config($names['old_name'], $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
1001 if ($old_data = $this->storageComparer->getTargetStorage($collection)->read($names['old_name'])) {
1002 $old_config->initWithData($old_data);
1005 $data = $this->storageComparer->getSourceStorage($collection)->read($names['new_name']);
1006 $new_config = new Config($names['new_name'], $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
1007 if ($data !== FALSE) {
1008 $new_config->setData($data);
1011 $entity_storage = $this->configManager->getEntityManager()->getStorage($entity_type_id);
1012 // Call to the configuration entity's storage to handle the configuration
1014 if (!($entity_storage instanceof ImportableEntityStorageInterface)) {
1015 throw new EntityStorageException(sprintf("The entity storage '%s' for the '%s' entity type does not support imports", get_class($entity_storage), $entity_type_id));
1017 $entity_storage->importRename($names['old_name'], $new_config, $old_config);
1018 $this->setProcessedConfiguration($collection, 'rename', $rename_name);
1023 * Determines if a import is already running.
1026 * TRUE if an import is already running, FALSE if not.
1028 public function alreadyImporting() {
1029 return !$this->lock->lockMayBeAvailable(static::LOCK_NAME);
1033 * Gets all the service dependencies from \Drupal.
1035 * Since the ConfigImporter handles module installation the kernel and the
1036 * container can be rebuilt and altered during processing. It is necessary to
1037 * keep the services used by the importer in sync.
1039 protected function reInjectMe() {
1040 $this->_serviceIds = [];
1041 $vars = get_object_vars($this);
1042 foreach ($vars as $key => $value) {
1043 if (is_object($value) && isset($value->_serviceId)) {
1044 $this->$key = \Drupal::service($value->_serviceId);