Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ConfigImporter.php 5.02 KiB
<?php

declare(strict_types=1);

namespace Drupal\config_enforce;

use Drupal\Core\Config\ConfigImporter as CoreConfigImporter;
use Drupal\Core\Config\ConfigException;
use Drupal\Core\Config\InstallStorage;
use Drupal\Core\Config\StorageComparer;
use Drupal\config\StorageReplaceDataWrapper;

/**
 * Imports configuration.
 */
class ConfigImporter {

  // Use log() method and related traits.
  use ConfigEnforceHelperTrait;

  // Channel with which to log from this class.
  const LOGCHANNEL = 'config_enforce\ConfigImporter';

  /**
   * Basic constructor.
   */
  public function __construct() { }

  /**
   * Imports a config item from the given filename.
   *
   * @param string $filename
   *   The filename to import from with the Drupal-relative path.
   */
  public function importConfig($filenames) {
    $config_storage = \Drupal::service('config.storage');
    $source_storage = new StorageReplaceDataWrapper($config_storage);

    foreach ($this->getDataFromConfigFiles($filenames) as $config_name => $data) {
      $source_storage->replaceData($config_name, $data);
    }

    $storage_comparer = new StorageComparer($source_storage, $config_storage);
    $storage_comparer->createChangelist();

    // @TODO log whether config has changed?

    return $this->doImport($storage_comparer);
  }

  protected function getDataFromConfigFiles($filenames) {
    $config_data = [];

    foreach ($filenames as $filename) {
      if (!is_readable($filename)) {
        $message = $this->t('The configuration file at :filename does not exist, or cannot be read.', [
          ':filename' => $filename,
        ]);
        // @TODO Log this to Drush too.
        $this->messenger()->addWarning($message);
        $this->log()->warning($message);
        continue;
      }
      $contents = file_get_contents($filename);
      $data = (new InstallStorage())->decode($contents);
      $config_name = basename($filename, '.yml');

      $this->handleSpecialCaseConfig($config_name, $data);

      $config_data[$config_name] = $data;
    }

    return $config_data;
  }

  // Copied from submitForm() at /core/modules/config/src/Form/ConfigSync.php
  protected function doImport($storage_comparer) {
    $config_importer = $this->getConfigImporter($storage_comparer);
    if ($config_importer->alreadyImporting()) {
      $this->log()->warning('Another request may be synchronizing configuration already.');
    } else {
      try {
        // This is the contents of \Drupal\Core\Config\ConfigImporter::import.
        // Copied here so we can log progress.
        if ($config_importer->hasUnprocessedConfigurationChanges()) {
          $sync_steps = $config_importer->initialize();
          foreach ($sync_steps as $step) {
            $context = [];
            do {
              $config_importer->doSyncStep($step, $context);
              if (isset($context['message'])) {
                $this->log()->notice(str_replace('Synchronizing', 'Synchronized', (string)$context['message']));
              }
            } while ($context['finished'] < 1);
          }
          // Clear the cache of the active config storage.
          \Drupal::service('cache.config')->deleteAll();
        }
        if ($config_importer->getErrors()) {
          throw new ConfigException('Errors occurred during import');
        } else {
          $this->log()->notice('The configuration was imported successfully.');
        }
      } 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.
        $message = 'The import failed due to the following reasons:' . "\n";
        $message .= implode("\n", $config_importer->getErrors());

        watchdog_exception('config_import', $e);
        throw new \Exception($message);
      }
    }
  }

  /**
   * Return an instantiated ConfigImporter.
   */
  protected function getConfigImporter($storage_comparer) {
    // @TODO: simplify this when
    // https://www.drupal.org/project/drupal/issues/3123491 is fixed.
    return new CoreConfigImporter(
      $storage_comparer,
      \Drupal::service('event_dispatcher'),
      \Drupal::service('config.manager'),
      \Drupal::service('lock.persistent'),
      \Drupal::service('config.typed'),
      \Drupal::service('module_handler'),
      \Drupal::service('module_installer'),
      \Drupal::service('theme_handler'),
      \Drupal::service('string_translation'),
      \Drupal::service('extension.list.module')
    );
  }

  /**
   * Alter data or otherwise handle config special cases.
   */
  protected function handleSpecialCaseConfig($config_name, &$data) {
    switch ($config_name) {
      case 'system.site':
        // Site UUID gets validated, so we need to make sure it'll match.
        // @TODO log this?
        $data['uuid'] = \Drupal::config('system.site')->get('uuid');
        break;
      default:
        return;
    }
  }

}