Issue #3047812 by bircher, Krzysztof Domański, alexpott, ricardoamaro, larowlan, borisson_, mpotter: Add a Config Transformation event dispatching during config import and export

merge-requests/1119/head
Alex Pott 2019-07-11 08:16:32 +01:00
parent 86736407cb
commit 3ae3e525b0
No known key found for this signature in database
GPG Key ID: 31905460D4A69276
18 changed files with 1077 additions and 0 deletions

View File

@ -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;
/**

View File

@ -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'

View File

@ -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 }

View File

@ -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);
}
}

View File

@ -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';
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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 }

View File

@ -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;
}
}

View File

@ -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'"));
}
}

View File

@ -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']);
}
}

View File

@ -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']);
}
}

View File

@ -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.');
}
}