Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
project
drupal
Commits
ea22b219
Commit
ea22b219
authored
Apr 13, 2014
by
webchick
Browse files
Issue
#2238069
by alexpott, xjm: Create a way to add steps to configuration sync.
parent
59dc3c3b
Changes
6
Hide whitespace changes
Inline
Side-by-side
core/lib/Drupal/Core/Config/BatchConfigImporter.php
deleted
100644 → 0
View file @
59dc3c3b
<?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
;
}
}
core/lib/Drupal/Core/Config/ConfigImporter.php
View file @
ea22b219
...
...
@@ -141,6 +141,20 @@ class ConfigImporter extends DependencySerialization {
*/
protected
$errors
=
array
();
/**
* 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
;
/**
* Constructs a configuration import object.
*
...
...
@@ -436,36 +450,207 @@ public function getUnprocessedExtensions($type) {
*/
public
function
import
()
{
if
(
$this
->
hasUnprocessedConfigurationChanges
())
{
$this
->
createExtensionChangelist
();
// Ensure that the changes have been validated.
$this
->
validate
();
$sync_steps
=
$this
->
initialize
();
if
(
!
$this
->
lock
->
acquire
(
static
::
LOCK_ID
))
{
// Another process is synchronizing configuration.
throw
new
ConfigImporterException
(
sprintf
(
'%s is already importing'
,
static
::
LOCK_ID
));
foreach
(
$sync_steps
as
$step
)
{
$context
=
array
();
do
{
$this
->
doSyncStep
(
$step
,
$context
);
}
while
(
$context
[
'finished'
]
<
1
);
}
}
return
$this
;
}
/**
* Calls a config import step.
*
* @param string|callable $sync_step
* The step to do. Either a method on the ConfigImporter class or a
* callable.
* @param array $context
* A batch context array. If the config importer is not running in a batch
* the only array key that is used is $context['finished']. A process needs
* to set $context['finished'] = 1 when it is done.
*
* @throws \InvalidArgumentException
* Exception thrown if the $sync_step can not be called.
*/
public
function
doSyncStep
(
$sync_step
,
&
$context
)
{
if
(
method_exists
(
$this
,
$sync_step
))
{
$this
->
$sync_step
(
$context
);
}
elseif
(
is_callable
(
$sync_step
))
{
call_user_func_array
(
$sync_step
,
array
(
&
$context
));
}
else
{
throw
new
\
InvalidArgumentException
(
'Invalid configuration synchronization step'
);
}
}
/**
* Initializes the config importer in preparation for processing a batch.
*
* @return array
* An array of \Drupal\Core\Config\ConfigImporter method names and callables
* that are invoked to complete the import. If there are modules or themes
* to process then an extra step is added.
*
* @throws \Drupal\Core\Config\ConfigImporterException
* If the configuration is already importing.
*/
public
function
initialize
()
{
$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
));
}
$sync_steps
=
array
();
$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
]);
}
// Process any extension changes before importing configuration.
$this
->
handleExtensions
();
// We have extensions to process.
if
(
$this
->
totalExtensionsToProcess
>
0
)
{
$sync_steps
[]
=
'processExtensions'
;
}
$sync_steps
[]
=
'processConfigurations'
;
// Allow modules to add new steps to configuration synchronization.
$this
->
moduleHandler
->
alter
(
'config_import_steps'
,
$sync_steps
);
$sync_steps
[]
=
'finish'
;
return
$sync_steps
;
}
// First pass deleted, then new, and lastly changed configuration, in order
// to handle dependencies correctly.
/**
* Processes extensions as a batch operation.
*
* @param array $context.
* The batch context.
*/
public
function
processExtensions
(
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
processConfigurations
(
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
)
{
foreach
(
$this
->
getUnprocessedConfiguration
(
$op
)
as
$name
)
{
if
(
$this
->
checkOp
(
$op
,
$name
))
{
$this
->
processConfiguration
(
$op
,
$name
);
}
$this
->
totalConfigurationToProcess
+=
count
(
$this
->
getUnprocessedConfiguration
(
$op
));
}
}
// Allow modules to react to a import.
$this
->
eventDispatcher
->
dispatch
(
ConfigEvents
::
IMPORT
,
new
ConfigImporterEvent
(
$this
));
}
$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
finish
(
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
;
}
// The import is now complete.
$this
->
lock
->
release
(
static
::
LOCK_ID
);
$this
->
reset
();
/**
* 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
]),
);
}
}
return
$this
;
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
;
}
/**
...
...
@@ -711,50 +896,6 @@ public function getId() {
return
static
::
LOCK_ID
;
}
/**
* Checks if a configuration object will be updated by the import.
*
* @param string $config_name
* The configuration object name.
*
* @return bool
* TRUE if the configuration object will be updated.
*/
protected
function
hasUpdate
(
$config_name
)
{
return
in_array
(
$config_name
,
$this
->
getUnprocessedConfiguration
(
'update'
));
}
/**
* Handle changes to installed modules and themes.
*/
protected
function
handleExtensions
()
{
$processed_extension
=
FALSE
;
foreach
(
array
(
'install'
,
'uninstall'
)
as
$op
)
{
$modules
=
$this
->
getUnprocessedExtensions
(
'module'
);
foreach
(
$modules
[
$op
]
as
$module
)
{
$processed_extension
=
TRUE
;
$this
->
processExtension
(
'module'
,
$op
,
$module
);
}
}
foreach
(
array
(
'enable'
,
'disable'
)
as
$op
)
{
$themes
=
$this
->
getUnprocessedExtensions
(
'theme'
);
foreach
(
$themes
[
$op
]
as
$theme
)
{
$processed_extension
=
TRUE
;
$this
->
processExtension
(
'theme'
,
$op
,
$theme
);
}
}
if
(
$processed_extension
)
{
// Recalculate differences as default config could have been imported.
$this
->
storageComparer
->
reset
();
$this
->
processed
=
$this
->
storageComparer
->
getEmptyChangelist
();
// Modules have been updated. Services etc might have changed.
// We don't reinject storage comparer because swapping out the active
// store during config import is a complete nonsense.
$this
->
recalculateChangelist
=
TRUE
;
}
}
/**
* Gets all the service dependencies from \Drupal.
*
...
...
core/modules/config/lib/Drupal/config/Form/ConfigSync.php
View file @
ea22b219
...
...
@@ -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
Batch
ConfigImporter
(
$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
{
$
operation
s
=
$config_importer
->
initialize
();
$
sync_step
s
=
$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
Batch
ConfigImporter $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
(
Batch
ConfigImporter
$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
();
...
...
core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php
View file @
ea22b219
...
...
@@ -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
();
...
...
core/modules/config/tests/config_import_test/config_import_test.module
View file @
ea22b219
...
...
@@ -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
;
}