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