Commit 0a93d9a1 authored by webchick's avatar webchick

Issue #2238069 by alexpott: Create a way to add steps to configuration sync.

parent e562ca4d
<?php
/**
* @file
* Contains \Drupal\Core\Config\BatchConfigImporter.
*/
namespace Drupal\Core\Config;
/**
* Defines a batch configuration importer.
*
* @see \Drupal\Core\Config\ConfigImporter
*/
class BatchConfigImporter extends ConfigImporter {
/**
* The total number of extensions to process.
*
* @var int
*/
protected $totalExtensionsToProcess = 0;
/**
* The total number of configuration objects to process.
*
* @var int
*/
protected $totalConfigurationToProcess = 0;
/**
* Initializes the config importer in preparation for processing a batch.
*
* @return array
* An array of method names that to be called by the batch. If there are
* modules or themes to process then an extra step is added.
*
* @throws ConfigImporterException
* If the configuration is already importing.
*/
public function initialize() {
$batch_operations = array();
$this->createExtensionChangelist();
// Ensure that the changes have been validated.
$this->validate();
if (!$this->lock->acquire(static::LOCK_ID)) {
// Another process is synchronizing configuration.
throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_ID));
}
$modules = $this->getUnprocessedExtensions('module');
foreach (array('install', 'uninstall') as $op) {
$this->totalExtensionsToProcess += count($modules[$op]);
}
$themes = $this->getUnprocessedExtensions('theme');
foreach (array('enable', 'disable') as $op) {
$this->totalExtensionsToProcess += count($themes[$op]);
}
// We have extensions to process.
if ($this->totalExtensionsToProcess > 0) {
$batch_operations[] = 'processExtensionBatch';
}
$batch_operations[] = 'processConfigurationBatch';
$batch_operations[] = 'finishBatch';
return $batch_operations;
}
/**
* Processes extensions as a batch operation.
*
* @param array $context.
* The batch context.
*/
public function processExtensionBatch(array &$context) {
$operation = $this->getNextExtensionOperation();
if (!empty($operation)) {
$this->processExtension($operation['type'], $operation['op'], $operation['name']);
$context['message'] = t('Synchronising extensions: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name']));
$processed_count = count($this->processedExtensions['module']['install']) + count($this->processedExtensions['module']['uninstall']);
$processed_count += count($this->processedExtensions['theme']['disable']) + count($this->processedExtensions['theme']['enable']);
$context['finished'] = $processed_count / $this->totalExtensionsToProcess;
}
else {
$context['finished'] = 1;
}
}
/**
* Processes configuration as a batch operation.
*
* @param array $context.
* The batch context.
*/
public function processConfigurationBatch(array &$context) {
// The first time this is called we need to calculate the total to process.
// This involves recalculating the changelist which will ensure that if
// extensions have been processed any configuration affected will be taken
// into account.
if ($this->totalConfigurationToProcess == 0) {
$this->storageComparer->reset();
foreach (array('delete', 'create', 'update') as $op) {
$this->totalConfigurationToProcess += count($this->getUnprocessedConfiguration($op));
}
}
$operation = $this->getNextConfigurationOperation();
if (!empty($operation)) {
if ($this->checkOp($operation['op'], $operation['name'])) {
$this->processConfiguration($operation['op'], $operation['name']);
}
$context['message'] = t('Synchronizing configuration: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name']));
$processed_count = count($this->processedConfiguration['create']) + count($this->processedConfiguration['delete']) + count($this->processedConfiguration['update']);
$context['finished'] = $processed_count / $this->totalConfigurationToProcess;
}
else {
$context['finished'] = 1;
}
}
/**
* Finishes the batch.
*
* @param array $context.
* The batch context.
*/
public function finishBatch(array &$context) {
$this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this));
// The import is now complete.
$this->lock->release(static::LOCK_ID);
$this->reset();
$context['message'] = t('Finalising configuration synchronisation.');
$context['finished'] = 1;
}
/**
* Gets the next extension operation to perform.
*
* @return array|bool
* An array containing the next operation and extension name to perform it
* on. If there is nothing left to do returns FALSE;
*/
protected function getNextExtensionOperation() {
foreach (array('install', 'uninstall') as $op) {
$modules = $this->getUnprocessedExtensions('module');
if (!empty($modules[$op])) {
return array(
'op' => $op,
'type' => 'module',
'name' => array_shift($modules[$op]),
);
}
}
foreach (array('enable', 'disable') as $op) {
$themes = $this->getUnprocessedExtensions('theme');
if (!empty($themes[$op])) {
return array(
'op' => $op,
'type' => 'theme',
'name' => array_shift($themes[$op]),
);
}
}
return FALSE;
}
/**
* Gets the next configuration operation to perform.
*
* @return array|bool
* An array containing the next operation and configuration name to perform
* it on. If there is nothing left to do returns FALSE;
*/
protected function getNextConfigurationOperation() {
// The order configuration operations is processed is important. Deletes
// have to come first so that recreates can work.
foreach (array('delete', 'create', 'update') as $op) {
$config_names = $this->getUnprocessedConfiguration($op);
if (!empty($config_names)) {
return array(
'op' => $op,
'name' => array_shift($config_names),
);
}
}
return FALSE;
}
}
......@@ -8,6 +8,7 @@
namespace Drupal\config\Form;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
......@@ -15,7 +16,6 @@
use Drupal\Core\Form\FormBase;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Config\BatchConfigImporter;
use Drupal\Core\Config\StorageComparer;
use Drupal\Core\Config\TypedConfigManager;
use Drupal\Core\Routing\UrlGeneratorInterface;
......@@ -243,7 +243,7 @@ public function buildForm(array $form, array &$form_state) {
* {@inheritdoc}
*/
public function submitForm(array &$form, array &$form_state) {
$config_importer = new BatchConfigImporter(
$config_importer = new ConfigImporter(
$form_state['storage_comparer'],
$this->eventDispatcher,
$this->configManager,
......@@ -257,7 +257,7 @@ public function submitForm(array &$form, array &$form_state) {
drupal_set_message($this->t('Another request may be synchronizing configuration already.'));
}
else{
$operations = $config_importer->initialize();
$sync_steps = $config_importer->initialize();
$batch = array(
'operations' => array(),
'finished' => array(get_class($this), 'finishBatch'),
......@@ -267,8 +267,8 @@ public function submitForm(array &$form, array &$form_state) {
'error_message' => t('Configuration synchronization has encountered an error.'),
'file' => drupal_get_path('module', 'config') . '/config.admin.inc',
);
foreach ($operations as $operation) {
$batch['operations'][] = array(array(get_class($this), 'processBatch'), array($config_importer, $operation));
foreach ($sync_steps as $sync_step) {
$batch['operations'][] = array(array(get_class($this), 'processBatch'), array($config_importer, $sync_step));
}
batch_set($batch);
......@@ -278,18 +278,20 @@ public function submitForm(array &$form, array &$form_state) {
/**
* Processes the config import batch and persists the importer.
*
* @param BatchConfigImporter $config_importer
* @param \Drupal\Core\Config\ConfigImporter $config_importer
* The batch config importer object to persist.
* @param string $sync_step
* The synchronisation step to do.
* @param $context
* The batch context.
*/
public static function processBatch(BatchConfigImporter $config_importer, $operation, &$context) {
public static function processBatch(ConfigImporter $config_importer, $sync_step, &$context) {
if (!isset($context['sandbox']['config_importer'])) {
$context['sandbox']['config_importer'] = $config_importer;
}
$config_importer = $context['sandbox']['config_importer'];
$config_importer->$operation($context);
$config_importer->doSyncStep($sync_step, $context);
if ($errors = $config_importer->getErrors()) {
if (!isset($context['results']['errors'])) {
$context['results']['errors'] = array();
......
......@@ -30,7 +30,7 @@ class ConfigImporterTest extends DrupalUnitTestBase {
*
* @var array
*/
public static $modules = array('config_test', 'system');
public static $modules = array('config_test', 'system', 'config_import_test');
public static function getInfo() {
return array(
......@@ -198,6 +198,10 @@ function testNew() {
$this->assertFalse(isset($GLOBALS['hook_config_test']['predelete']));
$this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
// Verify that hook_config_import_steps_alter() can add steps to
// configuration synchronization.
$this->assertTrue(isset($GLOBALS['hook_config_test']['config_import_steps_alter']));
// Verify that there is nothing more to import.
$this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
$logs = $this->configImporter->getErrors();
......
......@@ -4,3 +4,22 @@
* @file
* Provides configuration import test helpers.
*/
/**
* Implements hook_config_import_steps_alter().
*/
function config_import_test_config_import_steps_alter(&$sync_steps) {
$sync_steps[] = '_config_import_test_config_import_steps_alter';
}
/**
* Implements configuration synchronization step added by an alter for testing.
*
* @param array $context
* The batch context.
*/
function _config_import_test_config_import_steps_alter(&$context) {
$GLOBALS['hook_config_test']['config_import_steps_alter'] = TRUE;
$context['finished'] = 1;
return;
}
......@@ -2878,6 +2878,31 @@ function hook_link_alter(&$variables) {
}
}
/**
* Alter the configuration synchronization steps.
*
* @param array $sync_steps
* A one-dimensional array of \Drupal\Core\Config\ConfigImporter method names
* or callables that are invoked to complete the import, in the order that
* they will be processed. Each callable item defined in $sync_steps should
* either be a global function or a public static method. The callable should
* accept a $context array by reference. For example:
* <code>
* function _additional_configuration_step(&$context) {
* // Do stuff.
* // If finished set $context['finished'] = 1.
* }
* </code>
* For more information on creating batches, see the
* @link batch Batch operations @endlink documentation.
*
* @see callback_batch_operation()
* @see \Drupal\Core\Config\ConfigImporter::initialize()
*/
function hook_config_import_steps_alter(&$sync_steps) {
$sync_steps[] = '_additional_configuration_step';
}
/**
* @} End of "addtogroup hooks".
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment