Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Because hook_update_N + config == hard.
- Loading branch information
Showing
9 changed files
with
698 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
<?php | ||
|
||
namespace Drupal\commerce; | ||
|
||
|
||
use Drupal\Component\Utility\Crypt; | ||
use Drupal\Core\Config\ConfigFactoryInterface; | ||
use Drupal\Core\Config\ExtensionInstallStorage; | ||
use Drupal\Core\Config\InstallStorage; | ||
use Drupal\Core\Config\StorageInterface; | ||
use Drupal\Core\Entity\EntityTypeManagerInterface; | ||
use Drupal\Core\StringTranslation\StringTranslationTrait; | ||
|
||
/** | ||
* Class ConfigUpdater. | ||
*/ | ||
class ConfigUpdater implements ConfigUpdaterInterface { | ||
|
||
use StringTranslationTrait; | ||
/** | ||
* The entity type manager. | ||
* | ||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface | ||
*/ | ||
protected $entityTypeManager; | ||
|
||
/** | ||
* The active config storage. | ||
* | ||
* @var \Drupal\Core\Config\StorageInterface | ||
*/ | ||
protected $activeConfigStorage; | ||
|
||
/** | ||
* The extension config storage for config/install config items. | ||
* | ||
* @var \Drupal\Core\Config\StorageInterface | ||
*/ | ||
protected $extensionConfigStorage; | ||
|
||
/** | ||
* The extension config storage for config/optional config items. | ||
* | ||
* @var \Drupal\Core\Config\ExtensionInstallStorage | ||
*/ | ||
protected $extensionOptionalConfigStorage; | ||
|
||
/** | ||
* The config factory. | ||
* | ||
* @var \Drupal\Core\Config\ConfigFactoryInterface | ||
*/ | ||
protected $configFactory; | ||
|
||
/** | ||
* List of current config entity types, keyed by prefix. | ||
* | ||
* @var string[] | ||
*/ | ||
protected $typesByPrefix = []; | ||
|
||
/** | ||
* Constructs a ConfigReverter. | ||
* | ||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager | ||
* The entity type manager. | ||
* @param \Drupal\Core\Config\StorageInterface $active_config_storage | ||
* The active config storage. | ||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory | ||
* The config factory. | ||
*/ | ||
public function __construct(EntityTypeManagerInterface $entity_type_manager, StorageInterface $active_config_storage, ConfigFactoryInterface $config_factory) { | ||
$this->entityTypeManager = $entity_type_manager; | ||
$this->activeConfigStorage = $active_config_storage; | ||
$this->extensionConfigStorage = new ExtensionInstallStorage($active_config_storage, InstallStorage::CONFIG_INSTALL_DIRECTORY); | ||
$this->extensionOptionalConfigStorage = new ExtensionInstallStorage($active_config_storage, InstallStorage::CONFIG_OPTIONAL_DIRECTORY); | ||
$this->configFactory = $config_factory; | ||
|
||
foreach ($this->entityTypeManager->getDefinitions() as $entity_type => $definition) { | ||
if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) { | ||
/** @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface $definition */ | ||
$prefix = $definition->getConfigPrefix(); | ||
$this->typesByPrefix[$prefix] = $entity_type; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function import(array $config_names) { | ||
$succeeded = []; | ||
$failed = []; | ||
|
||
foreach ($config_names as $config_name) { | ||
// Read the config from the file. | ||
$value = $this->loadFromExtension($config_name); | ||
if (!$value) { | ||
$failed[$config_name] = $this->t('@config did not exist in extension storage', ['@config' => $config_name]); | ||
continue; | ||
} | ||
|
||
if ($this->loadFromActive($config_name)) { | ||
$failed[$config_name] = $this->t('@config already exists, use revert to update', ['@config' => $config_name]); | ||
continue; | ||
} | ||
|
||
$type = $this->getConfigType($config_name); | ||
|
||
// Save it as a new config entity or simple config. | ||
if ($type == 'system.simple') { | ||
$this->configFactory->getEditable($config_name)->setData($value)->save(); | ||
} | ||
else { | ||
/** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $entity_storage */ | ||
$entity_storage = $this->entityTypeManager->getStorage($type); | ||
$entity = $entity_storage->createFromStorageRecord($value); | ||
$entity->save(); | ||
} | ||
|
||
$succeeded[$config_name] = $this->t('@config was successfully imported', ['@config' => $config_name]); | ||
} | ||
|
||
return new ConfigUpdaterResult($failed, $succeeded); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function revert(array $config_names, $skip_modified = TRUE) { | ||
$succeeded = []; | ||
$failed = []; | ||
|
||
foreach ($config_names as $config_name) { | ||
// Read the config from the file. | ||
$value = $this->loadFromExtension($config_name); | ||
if (!$value) { | ||
$failed[$config_name] = $this->t('@config did not exist in extension storage', ['@config' => $config_name]); | ||
continue; | ||
} | ||
|
||
// Check the configuration object's hash and see if it has been modified. | ||
if ($this->isModified($config_name) && $skip_modified) { | ||
$failed[$config_name] = $this->t('@config has been modified and was not reverted', ['@config' => $config_name]); | ||
continue; | ||
} | ||
|
||
$type = $this->getConfigType($config_name); | ||
if ($type == 'system.simple') { | ||
// Load the current config and replace the value. | ||
$this->configFactory->getEditable($config_name)->setData($value)->save(); | ||
} | ||
else { | ||
// Load the current config entity and replace the value, with the | ||
// old UUID. | ||
$definition = $this->entityTypeManager->getDefinition($type); | ||
$id_key = $definition->getKey('id'); | ||
|
||
$id = $value[$id_key]; | ||
/** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $entity_storage */ | ||
$entity_storage = $this->entityTypeManager->getStorage($type); | ||
/** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */ | ||
$entity = $entity_storage->load($id); | ||
$uuid = $entity->get('uuid'); | ||
$entity = $entity_storage->updateFromStorageRecord($entity, $value); | ||
$entity->set('uuid', $uuid); | ||
$entity->save(); | ||
} | ||
|
||
$succeeded[$config_name] = $this->t('@config was successfully reverted', ['@config' => $config_name]); | ||
} | ||
|
||
return new ConfigUpdaterResult($failed, $succeeded); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function delete(array $config_names) { | ||
$succeeded = []; | ||
$failed = []; | ||
|
||
foreach ($config_names as $config_name) { | ||
$value = $this->loadFromActive($config_name); | ||
if (!$value) { | ||
$failed[$config_name] = $this->t('@config did not exist in extension storage', ['@config' => $config_name]); | ||
continue; | ||
} | ||
|
||
// Check the configuration object's hash and see if it has been modified. | ||
if ($this->isModified($config_name)) { | ||
$failed[$config_name] = $this->t('@config has been modified and was not deleted', ['@config' => $config_name]); | ||
continue; | ||
} | ||
|
||
$type = $this->getConfigType($config_name); | ||
if ($type == 'system.simple') { | ||
$config = $this->configFactory->getEditable($config_name); | ||
if (!$config) { | ||
$failed[$config_name] = $this->t('@config did not exist in active storage', ['@config' => $config_name]); | ||
continue; | ||
} | ||
$config->delete(); | ||
} | ||
else { | ||
$definition = $this->entityTypeManager->getDefinition($type); | ||
$id_key = $definition->getKey('id'); | ||
$id = $value[$id_key]; | ||
|
||
/** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $entity_storage */ | ||
$entity_storage = $this->entityTypeManager->getStorage($type); | ||
$entity = $entity_storage->load($id); | ||
$entity_storage->delete([$entity]); | ||
} | ||
|
||
$succeeded[$config_name] = $this->t('@config was successfully deleted', ['@config' => $config_name]); | ||
} | ||
|
||
return new ConfigUpdaterResult($failed, $succeeded); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function loadFromActive($config_name) { | ||
return $this->activeConfigStorage->read($config_name); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function loadFromExtension($config_name) { | ||
$value = $this->extensionConfigStorage->read($config_name); | ||
if (!$value) { | ||
$value = $this->extensionOptionalConfigStorage->read($config_name); | ||
} | ||
return $value; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function isModified($config_name) { | ||
// Read the configuration from active storage. | ||
$active = $this->activeConfigStorage->read($config_name); | ||
|
||
// Get the hash created when the config was installed. | ||
$original_hash = $active['_core']['default_config_hash']; | ||
|
||
// Remove export keys not used to generate default config hash. | ||
unset($active['uuid']); | ||
unset($active['_core']); | ||
|
||
// Recalculate the hash. | ||
$active_hash = Crypt::hashBase64(serialize($active)); | ||
|
||
return $original_hash !== $active_hash; | ||
} | ||
|
||
/** | ||
* Returns the config type for a given config object. | ||
* | ||
* @param string $config_name | ||
* Name of the config object. | ||
* | ||
* @return string | ||
* Name of the config type. | ||
*/ | ||
protected function getConfigType($config_name) { | ||
foreach ($this->typesByPrefix as $prefix => $config_type) { | ||
if (strpos($config_name, $prefix) === 0) { | ||
return $config_type; | ||
} | ||
} | ||
|
||
return NULL; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
<?php | ||
|
||
namespace Drupal\commerce; | ||
|
||
/** | ||
* Performs configuration updates on behalf of an extension. | ||
* | ||
* Service to provide methods for extensions to import, revert, or delete | ||
* configuration once they have been installed. Intended to be run from | ||
* hook_post_update_NAME(), since this involves the modification of entities. | ||
* | ||
* @see hook_post_update_NAME() | ||
*/ | ||
interface ConfigUpdaterInterface { | ||
|
||
/** | ||
* Imports configuration from extension storage to active storage. | ||
* | ||
* This action triggers a ConfigRevertInterface::IMPORT event if the | ||
* configuration could be imported. | ||
* | ||
* @param string[] $config_names | ||
* An array of configuration names. | ||
* | ||
* @return \Drupal\commerce\ConfigUpdaterResultInterface | ||
* Object containing failed and succeeded configuration object update. | ||
* May also throw exceptions if there is a problem during saving the | ||
* configuration. | ||
*/ | ||
public function import(array $config_names); | ||
|
||
/** | ||
* Reverts configuration to the value from extension storage. | ||
* | ||
* This action triggers a ConfigRevertInterface::REVERT event. | ||
* | ||
* @param string[] $config_names | ||
* An array of configuration names. | ||
* @param bool $skip_modified | ||
* Whether to skip modified configuration, defaults to TRUE. If this is | ||
* set to false, it will revert active configuration that has been | ||
* modified to the configuration values in the extension. | ||
* | ||
* @return \Drupal\commerce\ConfigUpdaterResultInterface | ||
* Object containing failed and succeeded configuration object update. | ||
* May also throw exceptions if there is a problem during saving the | ||
* configuration. | ||
*/ | ||
public function revert(array $config_names, $skip_modified = TRUE); | ||
|
||
/** | ||
* Deletes a configuration item. | ||
* | ||
* This action triggers a ConfigDeleteInterface::DELETE event. | ||
* | ||
* @param string[] $config_names | ||
* An array of configuration names. | ||
* | ||
* @return \Drupal\commerce\ConfigUpdaterResultInterface | ||
* Object containing failed and succeeded configuration object update. | ||
* May also throw exceptions if there is a problem during saving the | ||
* configuration. | ||
*/ | ||
public function delete(array $config_names); | ||
|
||
/** | ||
* Gets the current active value of configuration. | ||
* | ||
* @param string $config_name | ||
* The configuration item's full name. | ||
* | ||
* @return array | ||
* The configuration value. | ||
*/ | ||
public function loadFromActive($config_name); | ||
|
||
/** | ||
* Gets the extension storage value of configuration. | ||
* | ||
* This is the value from a file in the config/install or config/optional | ||
* directory of a module, theme, or install profile. | ||
* | ||
* @param string $config_name | ||
* The configuration item's full name. | ||
* | ||
* @return array|false | ||
* The configuration value, or FALSE if it could not be located. | ||
*/ | ||
public function loadFromExtension($config_name); | ||
|
||
/** | ||
* Compares a config item's has been modified since its installation. | ||
* | ||
* @param string $config_name | ||
* The configuration item's full name. | ||
* | ||
* @return bool | ||
* Returns TRUE is modified, FALSE if original configuration. | ||
*/ | ||
public function isModified($config_name); | ||
|
||
} |
Oops, something went wrong.