class ConfigImporter

Defines a configuration importer.

A config importer imports the changes into the configuration system. To determine which changes to import a StorageComparer in used.

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.

    • 'config.importer.import': Events listening can react to a successful import.

Hierarchy

Expanded class hierarchy of ConfigImporter

See also

\Drupal\Core\Config\StorageComparerInterface

\Drupal\Core\EventSubscriber\ConfigImportSubscriber

\Drupal\Core\EventSubscriber\ConfigSnapshotSubscriber

\Drupal\Core\Config\ConfigImporterEvent

4 files declare their use of ConfigImporter
config.admin.inc in drupal/core/modules/config/config.admin.inc
Admin page callbacks for the config module.
ConfigImporterTest.php in drupal/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php
Contains \Drupal\config\Tests\ConfigImporterTest.
TestBase.php in drupal/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
Definition of Drupal\simpletest\TestBase.
ViewTestConfigInstaller.php in drupal/core/modules/views/lib/Drupal/views/Tests/ViewTestConfigInstaller.php
Contains \Drupal\views\Tests\ViewTestConfigInstaller.

File

drupal/core/lib/Drupal/Core/Config/ConfigImporter.php, line 34
Contains \Drupal\Core\Config\ConfigImporter.

Namespace

Drupal\Core\Config
View source
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;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ConfigImporter::$configFactory protected property The configuration factory.
ConfigImporter::$context protected property The configuration context.
ConfigImporter::$entityManager protected property The plugin manager for entities.
ConfigImporter::$eventDispatcher protected property The event dispatcher used to notify subscribers.
ConfigImporter::$lock protected property The used lock backend instance.
ConfigImporter::$processed protected property List of changes processed by the import().
ConfigImporter::$storageComparer protected property The storage comparer used to discover configuration changes.
ConfigImporter::$validated protected property Indicates changes to import have been validated.
ConfigImporter::alreadyImporting public function Determines if a import is already running.
ConfigImporter::getId public function Returns the identifier for events and locks.
ConfigImporter::getProcessed public function Gets list of processed changes.
ConfigImporter::getStorageComparer public function Gets the configuration storage comparer.
ConfigImporter::getUnprocessed public function Gets a list of unprocessed changes for a given operation.
ConfigImporter::hasUnprocessedChanges public function Checks if there are any unprocessed changes.
ConfigImporter::ID constant The name used to identify events and the lock. 2
ConfigImporter::import public function Imports the changelist to the target storage.
ConfigImporter::importConfig protected function Writes an array of config changes from the source to the target storage.
ConfigImporter::importInvokeOwner protected function Invokes import* methods on configuration entity storage controllers.
ConfigImporter::notify protected function Dispatches a config importer event.
ConfigImporter::reset public function Resets the storage comparer and processed list.
ConfigImporter::setProcessed protected function Sets a change as processed.
ConfigImporter::validate public function Dispatches validate event for a ConfigImporter object.
ConfigImporter::__construct public function Constructs a configuration import object.