Issue #3047812 by bircher, Krzysztof Domański, alexpott, ricardoamaro, larowlan, borisson_, mpotter: Add a Config Transformation event dispatching during config import and export
							parent
							
								
									86736407cb
								
							
						
					
					
						commit
						3ae3e525b0
					
				| 
						 | 
				
			
			@ -5,6 +5,15 @@
 | 
			
		|||
 * Allows site administrators to modify environment configuration.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// Set class aliases for the classes that will go into core when we are in beta.
 | 
			
		||||
// See the experimental modules policy https://www.drupal.org/core/experimental
 | 
			
		||||
// @todo: remove class aliases in #2991683
 | 
			
		||||
@class_alias('Drupal\config_environment\Core\Config\StorageTransformEvent', 'Drupal\Core\Config\StorageTransformEvent');
 | 
			
		||||
@class_alias('Drupal\config_environment\Core\Config\ManagedStorage', 'Drupal\Core\Config\ManagedStorage');
 | 
			
		||||
@class_alias('Drupal\config_environment\Core\Config\StorageManagerInterface', 'Drupal\Core\Config\StorageManagerInterface');
 | 
			
		||||
@class_alias('Drupal\config_environment\Core\Config\ExportStorageManager', 'Drupal\Core\Config\ExportStorageManager');
 | 
			
		||||
@class_alias('Drupal\config_environment\Core\Config\ImportStorageTransformer', 'Drupal\Core\Config\ImportStorageTransformer');
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Routing\RouteMatchInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,31 @@
 | 
			
		|||
# @todo: Stop taking over config module routes in #2991683
 | 
			
		||||
config.sync:
 | 
			
		||||
  path: '/admin/config/development/configuration'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _form: '\Drupal\config_environment\Form\ConfigSync'
 | 
			
		||||
    _title: 'Synchronize'
 | 
			
		||||
  requirements:
 | 
			
		||||
    _permission: 'synchronize configuration'
 | 
			
		||||
 | 
			
		||||
config.diff:
 | 
			
		||||
  path: '/admin/config/development/configuration/sync/diff/{source_name}/{target_name}'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _controller: '\Drupal\config_environment\Controller\ConfigController::diff'
 | 
			
		||||
    target_name: NULL
 | 
			
		||||
  requirements:
 | 
			
		||||
    _permission: 'synchronize configuration'
 | 
			
		||||
 | 
			
		||||
config.diff_collection:
 | 
			
		||||
  path: '/admin/config/development/configuration/sync/diff_collection/{collection}/{source_name}/{target_name}'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _controller: '\Drupal\config_environment\Controller\ConfigController::diff'
 | 
			
		||||
    target_name: NULL
 | 
			
		||||
  requirements:
 | 
			
		||||
    _permission: 'synchronize configuration'
 | 
			
		||||
 | 
			
		||||
config.export_download:
 | 
			
		||||
  path: '/admin/config/development/configuration/full/export-download'
 | 
			
		||||
  defaults:
 | 
			
		||||
    _controller: '\Drupal\config_environment\Controller\ConfigController::downloadExport'
 | 
			
		||||
  requirements:
 | 
			
		||||
    _permission: 'export configuration'
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
services:
 | 
			
		||||
  # @todo: Move this back to core services in #2991683
 | 
			
		||||
  config.import_transformer:
 | 
			
		||||
    class: Drupal\config_environment\Core\Config\ImportStorageTransformer
 | 
			
		||||
    arguments: ['@event_dispatcher', '@database']
 | 
			
		||||
  config.storage.export:
 | 
			
		||||
    class: Drupal\config_environment\Core\Config\ManagedStorage
 | 
			
		||||
    arguments: ['@config.storage.export.manager']
 | 
			
		||||
  config.storage.export.manager:
 | 
			
		||||
    class: Drupal\config_environment\Core\Config\ExportStorageManager
 | 
			
		||||
    arguments: ['@config.storage', '@state', '@database', '@event_dispatcher']
 | 
			
		||||
    tags:
 | 
			
		||||
      - { name: event_subscriber }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,47 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\config_environment\Controller;
 | 
			
		||||
 | 
			
		||||
use Drupal\config\Controller\ConfigController as OriginalConfigController;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns responses for config module routes.
 | 
			
		||||
 */
 | 
			
		||||
class ConfigController extends OriginalConfigController {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The import transformer service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\ImportStorageTransformer
 | 
			
		||||
   */
 | 
			
		||||
  protected $importTransformer;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The sync storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $syncStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    $controller = parent::create($container);
 | 
			
		||||
    $controller->importTransformer = $container->get('config.import_transformer');
 | 
			
		||||
    $controller->syncStorage = $container->get('config.storage.sync');
 | 
			
		||||
 | 
			
		||||
    return $controller;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function diff($source_name, $target_name = NULL, $collection = NULL) {
 | 
			
		||||
    $this->sourceStorage = $this->importTransformer->transform($this->syncStorage);
 | 
			
		||||
 | 
			
		||||
    return parent::diff($source_name, $target_name, $collection);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,79 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
// @codingStandardsIgnoreStart
 | 
			
		||||
// @todo: Move this back to \Drupal\Core\Config\ConfigEvents in #2991683.
 | 
			
		||||
// @codingStandardsIgnoreEnd
 | 
			
		||||
namespace Drupal\config_environment\Core\Config;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines events for the configuration transform system.
 | 
			
		||||
 *
 | 
			
		||||
 * The constants in this class will be moved back into ConfigEvents.
 | 
			
		||||
 * But due to the fact that the config_environment is not in beta we save their
 | 
			
		||||
 * definitions here and use the literal strings in the mean time.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 *
 | 
			
		||||
 * @deprecated The class will be merged with Drupal\Core\Config\ConfigEvents.
 | 
			
		||||
 */
 | 
			
		||||
final class ConfigEvents {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Name of the event fired just before importing configuration.
 | 
			
		||||
   *
 | 
			
		||||
   * This event allows subscribers to modify the configuration which is about to
 | 
			
		||||
   * be imported. The event listener method receives a
 | 
			
		||||
   * \Drupal\Core\Config\StorageTransformEvent instance. This event contains a
 | 
			
		||||
   * config storage which subscribers can interact with and which will finally
 | 
			
		||||
   * be used to import the configuration from.
 | 
			
		||||
   * Together with \Drupal\Core\Config\ConfigEvents::STORAGE_TRANSFORM_EXPORT
 | 
			
		||||
   * subscribers can alter the active configuration in a config sync workflow
 | 
			
		||||
   * instead of just overriding at runtime via the config-override system.
 | 
			
		||||
   * This allows a complete customisation of the workflow including additional
 | 
			
		||||
   * modules and editable configuration in different environments.
 | 
			
		||||
   *
 | 
			
		||||
   * @code
 | 
			
		||||
   *   $storage = $event->getStorage();
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * This event is also fired when just viewing the difference of configuration
 | 
			
		||||
   * to be imported independently of whether the import takes place or not.
 | 
			
		||||
   * Use the \Drupal\Core\Config\ConfigEvents::IMPORT event to subscribe to the
 | 
			
		||||
   * import having taken place.
 | 
			
		||||
   *
 | 
			
		||||
   * @Event
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Config\StorageTransformEvent
 | 
			
		||||
   * @see \Drupal\Core\Config\ConfigEvents::STORAGE_TRANSFORM_EXPORT
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  const STORAGE_TRANSFORM_IMPORT = 'config.transform.import';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Name of the event fired when the export storage is used.
 | 
			
		||||
   *
 | 
			
		||||
   * This event allows subscribers to modify the configuration which is about to
 | 
			
		||||
   * be exported. The event listener method receives a
 | 
			
		||||
   * \Drupal\Core\Config\StorageTransformEvent instance. This event contains a
 | 
			
		||||
   * config storage which subscribers can interact with and which will finally
 | 
			
		||||
   * be used to export the configuration from.
 | 
			
		||||
   *
 | 
			
		||||
   * @code
 | 
			
		||||
   *   $storage = $event->getStorage();
 | 
			
		||||
   * @endcode
 | 
			
		||||
   *
 | 
			
		||||
   * Typically subscribers will want to perform the reverse operation on the
 | 
			
		||||
   * storage than for \Drupal\Core\Config\ConfigEvents::STORAGE_TRANSFORM_IMPORT
 | 
			
		||||
   * to make sure successive exports and imports yield no difference.
 | 
			
		||||
   *
 | 
			
		||||
   * @Event
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\Config\StorageTransformEvent
 | 
			
		||||
   * @see \Drupal\Core\Config\ConfigEvents::STORAGE_TRANSFORM_IMPORT
 | 
			
		||||
   *
 | 
			
		||||
   * @var string
 | 
			
		||||
   */
 | 
			
		||||
  const STORAGE_TRANSFORM_EXPORT = 'config.transform.export';
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,115 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
// @codingStandardsIgnoreStart
 | 
			
		||||
// @todo: Move this back to \Drupal\Core\Config in #2991683.
 | 
			
		||||
// Use this class with its class alias Drupal\Core\Config\ExportStorageManager
 | 
			
		||||
// @codingStandardsIgnoreEnd
 | 
			
		||||
namespace Drupal\config_environment\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\ConfigEvents;
 | 
			
		||||
use Drupal\Core\Config\DatabaseStorage;
 | 
			
		||||
use Drupal\Core\Config\ReadOnlyStorage;
 | 
			
		||||
use Drupal\Core\Config\StorageCopyTrait;
 | 
			
		||||
use Drupal\Core\Config\StorageInterface;
 | 
			
		||||
use Drupal\Core\Database\Connection;
 | 
			
		||||
use Drupal\Core\State\StateInterface;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The export storage manager dispatches an event for the export storage.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 */
 | 
			
		||||
class ExportStorageManager implements StorageManagerInterface, EventSubscriberInterface {
 | 
			
		||||
 | 
			
		||||
  use StorageCopyTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The state key indicating that the export storage needs to be rebuilt.
 | 
			
		||||
   */
 | 
			
		||||
  const NEEDS_REBUILD_KEY = 'config_export_needs_rebuild';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The active configuration storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $active;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The drupal state.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\State\StateInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $state;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The database storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\DatabaseStorage
 | 
			
		||||
   */
 | 
			
		||||
  protected $storage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The event dispatcher.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $eventDispatcher;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * ExportStorageManager constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\StorageInterface $active
 | 
			
		||||
   *   The active config storage to prime the export storage.
 | 
			
		||||
   * @param \Drupal\Core\State\StateInterface $state
 | 
			
		||||
   *   The drupal state.
 | 
			
		||||
   * @param \Drupal\Core\Database\Connection $connection
 | 
			
		||||
   *   The database connection.
 | 
			
		||||
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
 | 
			
		||||
   *   The event dispatcher.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(StorageInterface $active, StateInterface $state, Connection $connection, EventDispatcherInterface $event_dispatcher) {
 | 
			
		||||
    $this->active = $active;
 | 
			
		||||
    $this->state = $state;
 | 
			
		||||
    $this->eventDispatcher = $event_dispatcher;
 | 
			
		||||
    // The point of this service is to provide the storage and dispatch the
 | 
			
		||||
    // event when needed, so the storage itself can not be a service.
 | 
			
		||||
    $this->storage = new DatabaseStorage($connection, 'config_export');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getStorage() {
 | 
			
		||||
    if ($this->state->get(self::NEEDS_REBUILD_KEY, TRUE)) {
 | 
			
		||||
      self::replaceStorageContents($this->active, $this->storage);
 | 
			
		||||
      // @todo: Use ConfigEvents::STORAGE_TRANSFORM_EXPORT in #2991683
 | 
			
		||||
      $this->eventDispatcher->dispatch('config.transform.export', new StorageTransformEvent($this->storage));
 | 
			
		||||
      $this->state->set(self::NEEDS_REBUILD_KEY, FALSE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new ReadOnlyStorage($this->storage);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function getSubscribedEvents() {
 | 
			
		||||
    $events[ConfigEvents::SAVE][] = ['onConfigChange', 0];
 | 
			
		||||
    $events[ConfigEvents::DELETE][] = ['onConfigChange', 0];
 | 
			
		||||
    $events[ConfigEvents::RENAME][] = ['onConfigChange', 0];
 | 
			
		||||
    return $events;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the flag in state that the export storage is out of date.
 | 
			
		||||
   */
 | 
			
		||||
  public function onConfigChange() {
 | 
			
		||||
    if (!$this->state->get(self::NEEDS_REBUILD_KEY, FALSE)) {
 | 
			
		||||
      $this->state->set(self::NEEDS_REBUILD_KEY, TRUE);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,82 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
// @codingStandardsIgnoreStart
 | 
			
		||||
// @todo: Move this back to \Drupal\Core\Config in #2991683.
 | 
			
		||||
// Use this class with its class alias Drupal\Core\Config\ImportStorageTransformer
 | 
			
		||||
// @codingStandardsIgnoreEnd
 | 
			
		||||
namespace Drupal\config_environment\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Database\Connection;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 | 
			
		||||
use Drupal\Core\Config\DatabaseStorage;
 | 
			
		||||
use Drupal\Core\Config\StorageCopyTrait;
 | 
			
		||||
use Drupal\Core\Config\StorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class ImportStorageTransformer.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 */
 | 
			
		||||
class ImportStorageTransformer {
 | 
			
		||||
 | 
			
		||||
  use StorageCopyTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The event dispatcher to get changes to the configuration.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $eventDispatcher;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The drupal database connection.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Database\Connection
 | 
			
		||||
   */
 | 
			
		||||
  protected $connection;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * ImportStorageTransformer constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
 | 
			
		||||
   *   The event dispatcher.
 | 
			
		||||
   * @param \Drupal\Core\Database\Connection $connection
 | 
			
		||||
   *   The database connection.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(EventDispatcherInterface $event_dispatcher, Connection $connection) {
 | 
			
		||||
    $this->eventDispatcher = $event_dispatcher;
 | 
			
		||||
    $this->connection = $connection;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Transform the storage to be imported from.
 | 
			
		||||
   *
 | 
			
		||||
   * An import transformation is done before the config importer uses the
 | 
			
		||||
   * storage to synchronize the configuration. The transformation is also
 | 
			
		||||
   * done for displaying differences to review imports.
 | 
			
		||||
   * Importing in this context means the active drupal configuration is changed
 | 
			
		||||
   * with the ConfigImporter which may or may not be as part of the config
 | 
			
		||||
   * synchronization.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\StorageInterface $storage
 | 
			
		||||
   *   The storage to transform for importing from it.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   *   The transformed storage ready to be imported from.
 | 
			
		||||
   */
 | 
			
		||||
  public function transform(StorageInterface $storage) {
 | 
			
		||||
    // We use a database storage to reduce the memory requirement.
 | 
			
		||||
    $mutable = new DatabaseStorage($this->connection, 'config_import');
 | 
			
		||||
 | 
			
		||||
    // Copy the sync configuration to the created mutable storage.
 | 
			
		||||
    self::replaceStorageContents($storage, $mutable);
 | 
			
		||||
 | 
			
		||||
    // Dispatch the event so that event listeners can alter the configuration.
 | 
			
		||||
    // @todo: Use ConfigEvents::STORAGE_TRANSFORM_IMPORT in #2991683
 | 
			
		||||
    $this->eventDispatcher->dispatch('config.transform.import', new StorageTransformEvent($mutable));
 | 
			
		||||
 | 
			
		||||
    // Return the storage with the altered configuration.
 | 
			
		||||
    return $mutable;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,157 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
// @codingStandardsIgnoreStart
 | 
			
		||||
// @todo: Move this back to \Drupal\Core\Config in #2991683.
 | 
			
		||||
// Use this class with its class alias Drupal\Core\Config\ManagedStorage
 | 
			
		||||
// @codingStandardsIgnoreEnd
 | 
			
		||||
namespace Drupal\config_environment\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\StorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The managed storage defers all the storage method calls to the manager.
 | 
			
		||||
 *
 | 
			
		||||
 * The reason for deferring all the method calls is that the storage interface
 | 
			
		||||
 * is the API but we potentially need to do an expensive transformation before
 | 
			
		||||
 * the storage can be used so we can't do it in the constructor but we also
 | 
			
		||||
 * don't know which method is called first.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 */
 | 
			
		||||
class ManagedStorage implements StorageInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The decorated storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $storage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The storage manager to get the storage to decorate.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\StorageManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $manager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * ManagedStorage constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\StorageManagerInterface $manager
 | 
			
		||||
   *   The storage manager.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(StorageManagerInterface $manager) {
 | 
			
		||||
    $this->manager = $manager;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function exists($name) {
 | 
			
		||||
    return $this->getStorage()->exists($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function read($name) {
 | 
			
		||||
    return $this->getStorage()->read($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function readMultiple(array $names) {
 | 
			
		||||
    return $this->getStorage()->readMultiple($names);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function write($name, array $data) {
 | 
			
		||||
    return $this->getStorage()->write($name, $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function delete($name) {
 | 
			
		||||
    return $this->getStorage()->delete($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function rename($name, $new_name) {
 | 
			
		||||
    return $this->getStorage()->rename($name, $new_name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function encode($data) {
 | 
			
		||||
    return $this->getStorage()->encode($data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function decode($raw) {
 | 
			
		||||
    return $this->getStorage()->decode($raw);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function listAll($prefix = '') {
 | 
			
		||||
    return $this->getStorage()->listAll($prefix);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function deleteAll($prefix = '') {
 | 
			
		||||
    return $this->getStorage()->deleteAll($prefix);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function createCollection($collection) {
 | 
			
		||||
    // We return the collection directly.
 | 
			
		||||
    // This means that the collection will not be an instance of ManagedStorage
 | 
			
		||||
    // But this doesn't matter because the storage is retrieved from the
 | 
			
		||||
    // manager only the first time it is accessed.
 | 
			
		||||
    return $this->getStorage()->createCollection($collection);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getAllCollectionNames() {
 | 
			
		||||
    return $this->getStorage()->getAllCollectionNames();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getCollectionName() {
 | 
			
		||||
    return $this->getStorage()->getCollectionName();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the decorated storage from the manager if necessary.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   *   The config storage.
 | 
			
		||||
   */
 | 
			
		||||
  protected function getStorage() {
 | 
			
		||||
    // Get the storage from the manager the first time it is needed.
 | 
			
		||||
    if (!isset($this->storage)) {
 | 
			
		||||
      $this->storage = $this->manager->getStorage();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $this->storage;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
// @codingStandardsIgnoreStart
 | 
			
		||||
// @todo: Move this back to \Drupal\Core\Config in #2991683.
 | 
			
		||||
// Use this class with its class alias Drupal\Core\Config\StorageManagerInterface
 | 
			
		||||
// @codingStandardsIgnoreEnd
 | 
			
		||||
namespace Drupal\config_environment\Core\Config;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface for a storage manager.
 | 
			
		||||
 *
 | 
			
		||||
 * @internal
 | 
			
		||||
 */
 | 
			
		||||
interface StorageManagerInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the config storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   *   The config storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function getStorage();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,52 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
// @codingStandardsIgnoreStart
 | 
			
		||||
// @todo: Move this back to \Drupal\Core\Config in #2991683.
 | 
			
		||||
// Use this class with its class alias Drupal\Core\Config\StorageTransformEvent
 | 
			
		||||
// @codingStandardsIgnoreEnd
 | 
			
		||||
namespace Drupal\config_environment\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\EventDispatcher\Event;
 | 
			
		||||
// @todo: below removed when namespace is \Drupal\Core\Config in 2991683.
 | 
			
		||||
use Drupal\Core\Config\StorageInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class StorageTransformEvent.
 | 
			
		||||
 *
 | 
			
		||||
 * This event allows subscribers to alter the configuration of the storage that
 | 
			
		||||
 * is being transformed.
 | 
			
		||||
 */
 | 
			
		||||
class StorageTransformEvent extends Event {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The configuration storage which is transformed.
 | 
			
		||||
   *
 | 
			
		||||
   * This storage can be interacted with by event subscribers and will be
 | 
			
		||||
   * used instead of the original storage after all event subscribers have been
 | 
			
		||||
   * called.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $storage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * StorageTransformEvent constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\StorageInterface $storage
 | 
			
		||||
   *   The storage with the configuration to transform.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(StorageInterface $storage) {
 | 
			
		||||
    $this->storage = $storage;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the mutable storage ready to be read from and written to.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   *   The config storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function getStorage() {
 | 
			
		||||
    return $this->storage;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,48 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\config_environment\Form;
 | 
			
		||||
 | 
			
		||||
use Drupal\config\Form\ConfigSync as OriginalConfigSync;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Overrides the ConfigSync form.
 | 
			
		||||
 */
 | 
			
		||||
class ConfigSync extends OriginalConfigSync {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The import transformer service.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\ImportStorageTransformer
 | 
			
		||||
   */
 | 
			
		||||
  protected $importTransformer;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The sync storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $originalSyncStorage;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function create(ContainerInterface $container) {
 | 
			
		||||
    $form = parent::create($container);
 | 
			
		||||
    $form->importTransformer = $container->get('config.import_transformer');
 | 
			
		||||
    $form->originalSyncStorage = $form->syncStorage;
 | 
			
		||||
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $this->syncStorage = $this->importTransformer->transform($this->originalSyncStorage);
 | 
			
		||||
 | 
			
		||||
    return parent::buildForm($form, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
# @todo: Move this test module under the config module in #2991683.
 | 
			
		||||
name: 'Configuration Storage Transformer Test'
 | 
			
		||||
type: module
 | 
			
		||||
package: Testing
 | 
			
		||||
version: VERSION
 | 
			
		||||
core: 8.x
 | 
			
		||||
dependencies:
 | 
			
		||||
  - drupal:config
 | 
			
		||||
# @todo: remove dependency on config_environment in #2991683.
 | 
			
		||||
  - drupal:config_environment
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
services:
 | 
			
		||||
  config_transformer_test.event_subscriber:
 | 
			
		||||
    class: Drupal\config_transformer_test\EventSubscriber
 | 
			
		||||
    arguments: ['@config.storage', '@config.storage.sync']
 | 
			
		||||
    tags:
 | 
			
		||||
      - { name: event_subscriber }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,102 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Drupal\config_transformer_test;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\StorageInterface;
 | 
			
		||||
use Drupal\Core\Config\StorageTransformEvent;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class EventSubscriber.
 | 
			
		||||
 *
 | 
			
		||||
 * The transformations here are for testing purposes only and do not constitute
 | 
			
		||||
 * a well-behaved config storage transformation.
 | 
			
		||||
 */
 | 
			
		||||
class EventSubscriber implements EventSubscriberInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The active config storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $active;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The sync config storage.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Config\StorageInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $sync;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * EventSubscriber constructor.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\StorageInterface $active
 | 
			
		||||
   *   The active config storage.
 | 
			
		||||
   * @param \Drupal\Core\Config\StorageInterface $sync
 | 
			
		||||
   *   The sync config storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function __construct(StorageInterface $active, StorageInterface $sync) {
 | 
			
		||||
    $this->active = $active;
 | 
			
		||||
    $this->sync = $sync;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The storage is transformed for importing.
 | 
			
		||||
   *
 | 
			
		||||
   * In this transformation we ignore the site name from the sync storage and
 | 
			
		||||
   * set it always to the currently active site name with an additional string
 | 
			
		||||
   * so that there will always be something to import.
 | 
			
		||||
   * Do not do this outside of tests.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\StorageTransformEvent $event
 | 
			
		||||
   *   The config storage transform event.
 | 
			
		||||
   */
 | 
			
		||||
  public function onImportTransform(StorageTransformEvent $event) {
 | 
			
		||||
    $storage = $event->getStorage();
 | 
			
		||||
    $site = $storage->read('system.site');
 | 
			
		||||
    // Only change something if the sync storage has data.
 | 
			
		||||
    if (!empty($site)) {
 | 
			
		||||
      // Add "Arrr" to the site name. Because pirates!
 | 
			
		||||
      // The site name which is in the sync directory will be ignored.
 | 
			
		||||
      $current = $this->active->read('system.site');
 | 
			
		||||
      $site['name'] = $current['name'] . ' Arrr';
 | 
			
		||||
      $storage->write('system.site', $site);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The storage is transformed for exporting.
 | 
			
		||||
   *
 | 
			
		||||
   * In this transformation we ignore the site slogan from the site if the sync
 | 
			
		||||
   * storage has it. Just export it again with an additional string so that
 | 
			
		||||
   * there will always be something new exported.
 | 
			
		||||
   * Do not do this outside of tests.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\StorageTransformEvent $event
 | 
			
		||||
   *   The config storage transform event.
 | 
			
		||||
   */
 | 
			
		||||
  public function onExportTransform(StorageTransformEvent $event) {
 | 
			
		||||
    $sync = $this->sync->read('system.site');
 | 
			
		||||
    // Only change something if the sync storage has data.
 | 
			
		||||
    if (!empty($sync)) {
 | 
			
		||||
      $storage = $event->getStorage();
 | 
			
		||||
      $site = $storage->read('system.site');
 | 
			
		||||
      // Add "Arrr" to the site slogan. Because pirates!
 | 
			
		||||
      // The active slogan will be ignored.
 | 
			
		||||
      $site['slogan'] = $sync['slogan'] . ' Arrr';
 | 
			
		||||
      $storage->write('system.site', $site);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static function getSubscribedEvents() {
 | 
			
		||||
    // @todo: use class constants when they get added in #2991683
 | 
			
		||||
    $events['config.transform.import'][] = ['onImportTransform'];
 | 
			
		||||
    $events['config.transform.export'][] = ['onExportTransform'];
 | 
			
		||||
    return $events;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,100 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
// @codingStandardsIgnoreStart
 | 
			
		||||
// @todo: Move this back to \Drupal\Tests\config\Functional in #2991683.
 | 
			
		||||
// @codingStandardsIgnoreEnd
 | 
			
		||||
namespace Drupal\Tests\config_environment\Functional;
 | 
			
		||||
 | 
			
		||||
use Drupal\Component\Utility\Html;
 | 
			
		||||
use Drupal\Tests\BrowserTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the user interface for importing/exporting transformed configuration.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class TransformedConfigExportImportUITest extends BrowserTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public static $modules = [
 | 
			
		||||
    'config',
 | 
			
		||||
    'config_transformer_test',
 | 
			
		||||
    'config_environment',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp() {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    $permissions = [
 | 
			
		||||
      'export configuration',
 | 
			
		||||
      'import configuration',
 | 
			
		||||
      'synchronize configuration',
 | 
			
		||||
    ];
 | 
			
		||||
    $this->webUser = $this->drupalCreateUser($permissions);
 | 
			
		||||
    $this->drupalLogin($this->webUser);
 | 
			
		||||
 | 
			
		||||
    // Start off with the sync storage being the same as the active storage.
 | 
			
		||||
    $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests a simple site export import case.
 | 
			
		||||
   */
 | 
			
		||||
  public function testTransformedExportImport() {
 | 
			
		||||
    // After installation there is no snapshot but a new site name.
 | 
			
		||||
    $this->drupalGet('admin/config/development/configuration');
 | 
			
		||||
    $this->assertNoText('Warning message');
 | 
			
		||||
    $this->assertNoText('There are no configuration changes to import.');
 | 
			
		||||
 | 
			
		||||
    // Tests changes of system.site.
 | 
			
		||||
    $this->drupalGet('admin/config/development/configuration/sync/diff/system.site');
 | 
			
		||||
    $this->assertText('name: Drupal');
 | 
			
		||||
    $this->assertText(Html::escape("name: 'Drupal Arrr'"));
 | 
			
		||||
 | 
			
		||||
    // Add a slogan.
 | 
			
		||||
    $originalSlogan = $this->config('system.site')->get('slogan');
 | 
			
		||||
    $this->assertEmpty($originalSlogan);
 | 
			
		||||
    $newSlogan = $this->randomMachineName(16);
 | 
			
		||||
    $this->assertNotEqual($newSlogan, $originalSlogan);
 | 
			
		||||
    $this->config('system.site')
 | 
			
		||||
      ->set('slogan', $newSlogan)
 | 
			
		||||
      ->save();
 | 
			
		||||
    $this->assertEqual($this->config('system.site')->get('slogan'), $newSlogan);
 | 
			
		||||
 | 
			
		||||
    // Tests changes of system.site.
 | 
			
		||||
    $this->drupalGet('admin/config/development/configuration/sync/diff/system.site');
 | 
			
		||||
    $this->assertText(Html::escape("slogan: ''"));
 | 
			
		||||
    $this->assertText(Html::escape("slogan: $newSlogan"));
 | 
			
		||||
 | 
			
		||||
    // Export the configuration.
 | 
			
		||||
    $this->drupalPostForm('admin/config/development/configuration/full/export', [], 'Export');
 | 
			
		||||
    $tarball = $this->getSession()->getPage()->getContent();
 | 
			
		||||
 | 
			
		||||
    // Import the configuration from the tarball.
 | 
			
		||||
    $filename = 'temporary://' . $this->randomMachineName();
 | 
			
		||||
    file_put_contents($filename, $tarball);
 | 
			
		||||
    $this->drupalPostForm('admin/config/development/configuration/full/import', ['files[import_tarball]' => $filename], 'Upload');
 | 
			
		||||
 | 
			
		||||
    // Assert the new name and slogan.
 | 
			
		||||
    $this->drupalGet('admin/config/development/configuration/sync/diff/system.site');
 | 
			
		||||
    $this->assertText(Html::escape("name: 'Drupal Arrr'"));
 | 
			
		||||
    $this->assertText(Html::escape("slogan: '$originalSlogan Arrr'"));
 | 
			
		||||
    $this->assertEqual($this->config('system.site')->get('name'), 'Drupal');
 | 
			
		||||
    $this->assertEqual($this->config('system.site')->get('slogan'), $newSlogan);
 | 
			
		||||
 | 
			
		||||
    // Sync the configuration.
 | 
			
		||||
    $this->drupalPostForm('admin/config/development/configuration', [], 'Import all');
 | 
			
		||||
    $this->assertEqual($this->config('system.site')->get('name'), 'Drupal Arrr');
 | 
			
		||||
    $this->assertEqual($this->config('system.site')->get('slogan'), $originalSlogan . " Arrr");
 | 
			
		||||
 | 
			
		||||
    // Assert that the event was dispatched again on the new config.
 | 
			
		||||
    $this->drupalGet('admin/config/development/configuration/sync/diff/system.site');
 | 
			
		||||
    $this->assertText(Html::escape("name: 'Drupal Arrr Arrr'"));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,62 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
// @codingStandardsIgnoreStart
 | 
			
		||||
// @todo: Move this back to \Drupal\KernelTests\Core\Config in #2991683.
 | 
			
		||||
// @codingStandardsIgnoreEnd
 | 
			
		||||
namespace Drupal\Tests\config_environment\Kernel\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the export storage manager.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ExportStorageManagerTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'config_transformer_test',
 | 
			
		||||
    'config_environment',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp() {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(['system']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test getting the export storage.
 | 
			
		||||
   */
 | 
			
		||||
  public function testGetStorage() {
 | 
			
		||||
    // Get the raw system.site config and set it in the sync storage.
 | 
			
		||||
    $rawConfig = $this->config('system.site')->getRawData();
 | 
			
		||||
    $this->container->get('config.storage.sync')->write('system.site', $rawConfig);
 | 
			
		||||
 | 
			
		||||
    $storage = $this->container->get('config.storage.export.manager')->getStorage();
 | 
			
		||||
    $exported = $storage->read('system.site');
 | 
			
		||||
    // The test subscriber adds "Arrr" to the slogan of the sync config.
 | 
			
		||||
    $this->assertEquals($rawConfig['name'], $exported['name']);
 | 
			
		||||
    $this->assertEquals($rawConfig['slogan'] . ' Arrr', $exported['slogan']);
 | 
			
		||||
 | 
			
		||||
    // Save the config to trigger the rebuild.
 | 
			
		||||
    $this->config('system.site')
 | 
			
		||||
      ->set('name', 'New name')
 | 
			
		||||
      ->set('slogan', 'New slogan')
 | 
			
		||||
      ->save();
 | 
			
		||||
 | 
			
		||||
    // Get the storage again.
 | 
			
		||||
    $storage = $this->container->get('config.storage.export.manager')->getStorage();
 | 
			
		||||
    $exported = $storage->read('system.site');
 | 
			
		||||
    // The test subscriber adds "Arrr" to the slogan of the sync config.
 | 
			
		||||
    $this->assertEquals('New name', $exported['name']);
 | 
			
		||||
    $this->assertEquals($rawConfig['slogan'] . ' Arrr', $exported['slogan']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
// @codingStandardsIgnoreStart
 | 
			
		||||
// @todo: Move this back to \Drupal\KernelTests\Core\Config in #2991683.
 | 
			
		||||
// @codingStandardsIgnoreEnd
 | 
			
		||||
namespace Drupal\Tests\config_environment\Kernel\Core\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\MemoryStorage;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the import storage transformer.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ImportStorageTransformerTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'config_transformer_test',
 | 
			
		||||
    'config_environment',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp() {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->installConfig(['system']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test the import transformation.
 | 
			
		||||
   */
 | 
			
		||||
  public function testTransform() {
 | 
			
		||||
    // Get the raw system.site config and set it in the sync storage.
 | 
			
		||||
    $rawConfig = $this->config('system.site')->getRawData();
 | 
			
		||||
 | 
			
		||||
    $storage = new MemoryStorage();
 | 
			
		||||
    $this->copyConfig($this->container->get('config.storage'), $storage);
 | 
			
		||||
 | 
			
		||||
    $import = $this->container->get('config.import_transformer')->transform($storage);
 | 
			
		||||
    $config = $import->read('system.site');
 | 
			
		||||
    // The test subscriber always adds "Arrr" to the current site name.
 | 
			
		||||
    $this->assertEquals($rawConfig['name'] . ' Arrr', $config['name']);
 | 
			
		||||
    $this->assertEquals($rawConfig['slogan'], $config['slogan']);
 | 
			
		||||
 | 
			
		||||
    // Update the site config in the storage to test a second transformation.
 | 
			
		||||
    $config['name'] = 'New name';
 | 
			
		||||
    $config['slogan'] = 'New slogan';
 | 
			
		||||
    $storage->write('system.site', $config);
 | 
			
		||||
 | 
			
		||||
    $import = $this->container->get('config.import_transformer')->transform($storage);
 | 
			
		||||
    $config = $import->read('system.site');
 | 
			
		||||
    // The test subscriber always adds "Arrr" to the current site name.
 | 
			
		||||
    $this->assertEquals($rawConfig['name'] . ' Arrr', $config['name']);
 | 
			
		||||
    $this->assertEquals('New slogan', $config['slogan']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,77 @@
 | 
			
		|||
<?php
 | 
			
		||||
 | 
			
		||||
// @codingStandardsIgnoreStart
 | 
			
		||||
// @todo: Move this back to \Drupal\KernelTests\Core\Config\Storage in #2991683.
 | 
			
		||||
// @codingStandardsIgnoreEnd
 | 
			
		||||
namespace Drupal\Tests\config_environment\Kernel\Core\Config\Storage;
 | 
			
		||||
 | 
			
		||||
// include class aliases. @todo: remove in #2991683.
 | 
			
		||||
include_once __DIR__ . '/../../../../../../config_environment.module';
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\StorageManagerInterface;
 | 
			
		||||
use Drupal\Core\Config\ManagedStorage;
 | 
			
		||||
use Drupal\Core\Config\MemoryStorage;
 | 
			
		||||
use Drupal\KernelTests\Core\Config\Storage\ConfigStorageTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests ManagedStorage operations.
 | 
			
		||||
 *
 | 
			
		||||
 * @group config
 | 
			
		||||
 */
 | 
			
		||||
class ManagedStorageTest extends ConfigStorageTestBase implements StorageManagerInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getStorage() {
 | 
			
		||||
    // We return a new storage every time to make sure the managed storage
 | 
			
		||||
    // only calls this once and retains the configuration by itself.
 | 
			
		||||
    return new MemoryStorage();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp() {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    $this->storage = new ManagedStorage($this);
 | 
			
		||||
    // ::listAll() verifications require other configuration data to exist.
 | 
			
		||||
    $this->storage->write('system.performance', []);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function read($name) {
 | 
			
		||||
    return $this->storage->read($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function insert($name, $data) {
 | 
			
		||||
    $this->storage->write($name, $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function update($name, $data) {
 | 
			
		||||
    $this->storage->write($name, $data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function delete($name) {
 | 
			
		||||
    $this->storage->delete($name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidStorage() {
 | 
			
		||||
    $this->markTestSkipped('ManagedStorage cannot be invalid.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue