Contains \Drupal\Core\Config\ConfigImporter.
<?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;
}
}
Name | Description |
---|---|
ConfigImporter | Defines a configuration importer. |