UpdateHookRegistry.php
Same filename in other branches
Namespace
Drupal\Core\UpdateFile
-
core/
lib/ Drupal/ Core/ Update/ UpdateHookRegistry.php
View source
<?php
namespace Drupal\Core\Update;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
/**
* Provides module updates versions handling.
*/
class UpdateHookRegistry {
/**
* Indicates that a module has not been installed yet.
*/
public const SCHEMA_UNINSTALLED = -1;
/**
* Regular expression to match all possible defined hook_update_N().
*/
private const FUNC_NAME_REGEXP = '/^(?<module>.+)_update_(?<version>\\d+)$/';
/**
* A list of enabled modules.
*
* @var string[]
*/
protected $enabledModules;
/**
* The system.schema key value storage.
*
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
*/
protected $keyValue;
/**
* The core.equivalent_updates key value storage.
*
* The key value keys are modules and the value is an array of equivalent
* updates with the following shape:
* - The array keys are the equivalent future update numbers.
* - The value is an array containing two keys:
* - 'ran_update': The update that registered the future update as an
* equivalent.
* - 'future_version_string': The version that provides the future update.
*
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
* @see module.api.php
*/
protected KeyValueStoreInterface $equivalentUpdates;
/**
* A static cache of schema currentVersions per module.
*
* Stores schema versions of the modules based on their defined hook_update_N
* implementations.
* Example:
* ```
* [
* 'example_module' => [
* 8000,
* 8001,
* 8002
* ]
* ]
* ```
*
* @var int[][]
* @see \Drupal\Core\Update\UpdateHookRegistry::getAvailableUpdates()
*/
protected $allAvailableSchemaVersions = [];
/**
* Constructs a new UpdateHookRegistry.
*
* @param array $module_list
* An associative array whose keys are the names of installed modules.
* @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface|\Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
* The key value factory.
*/
public function __construct(array $module_list, KeyValueStoreInterface|KeyValueFactoryInterface $key_value_factory) {
if ($module_list !== [] && array_is_list($module_list)) {
@trigger_error('Calling ' . __METHOD__ . '() with the $enabled_modules argument is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use an associative array whose keys are the names of installed modules instead. See https://www.drupal.org/node/3423659', E_USER_DEPRECATED);
$module_list = \Drupal::service('module_handler')->getModuleList();
}
if ($key_value_factory instanceof KeyValueStoreInterface) {
@trigger_error('Calling ' . __METHOD__ . '() with the $key_value_factory argument as a KeyValueStoreInterface instead of a KeyValueFactoryInterface is deprecated in drupal:10.3.0 and it will be required in drupal:11.0.0. See https://www.drupal.org/node/3423659', E_USER_DEPRECATED);
$key_value_factory = \Drupal::service('keyvalue');
}
$this->enabledModules = array_keys($module_list);
$this->keyValue = $key_value_factory->get('system.schema');
$this->equivalentUpdates = $key_value_factory->get('core.equivalent_updates');
}
/**
* Returns an array of available schema versions for a module.
*
* @param string $module
* A module name.
*
* @return int[]
* An array of available updates sorted by version. Empty array returned if
* no updates available.
*/
public function getAvailableUpdates(string $module) : array {
if (!isset($this->allAvailableSchemaVersions[$module])) {
$this->allAvailableSchemaVersions[$module] = [];
foreach ($this->enabledModules as $enabled_module) {
$this->allAvailableSchemaVersions[$enabled_module] = [];
}
$functions = get_defined_functions();
// Narrow this down to functions ending with an integer, since all
// hook_update_N() functions end this way, and there are other
// possible functions which match '_update_'. We use preg_grep() here
// since looping through all PHP functions can take significant page
// execution time and this function is called on every administrative page
// via system_requirements().
foreach (preg_grep('/_\\d+$/', $functions['user']) as $function) {
// If this function is a module update function, add it to the list of
// module updates.
if (preg_match(self::FUNC_NAME_REGEXP, $function, $matches)) {
$this->allAvailableSchemaVersions[$matches['module']][] = (int) $matches['version'];
}
}
// Ensure that updates are applied in numerical order.
array_walk($this->allAvailableSchemaVersions, static function (&$module_updates) {
sort($module_updates, SORT_NUMERIC);
});
}
return $this->allAvailableSchemaVersions[$module];
}
/**
* Returns the currently installed schema version for a module.
*
* @param string $module
* A module name.
*
* @return int
* The currently installed schema version, or self::SCHEMA_UNINSTALLED if the
* module is not installed.
*/
public function getInstalledVersion(string $module) : int {
return $this->keyValue
->get($module, self::SCHEMA_UNINSTALLED);
}
/**
* Updates the installed version information for a module.
*
* @param string $module
* A module name.
* @param int $version
* The new schema version.
*
* @return self
* Returns self to support chained method calls.
*/
public function setInstalledVersion(string $module, int $version) : self {
$this->keyValue
->set($module, $version);
$this->deleteEquivalentUpdate($module, $version);
return $this;
}
/**
* Deletes the installed version information for the module.
*
* @param string $module
* The module name to delete.
*/
public function deleteInstalledVersion(string $module) : void {
$this->keyValue
->delete($module);
$this->equivalentUpdates
->delete($module);
}
/**
* Returns the currently installed schema version for all modules.
*
* @return int[]
* Array of modules as the keys and values as the currently installed
* schema version of corresponding module, or self::SCHEMA_UNINSTALLED if the
* module is not installed.
*/
public function getAllInstalledVersions() : array {
return $this->keyValue
->getAll();
}
/**
* Marks a future update as equivalent to the current update running.
*
* Updates can be marked as equivalent when they are backported to a
* previous, but still supported, major version. For example:
* - A 2.x hook_update_N() would be added as normal, for example:
* MODULE_update_2005().
* - When that same update is backported to 1.x, it is given its own update
* number, for example: MODULE_update_1040(). In this update, a call to
* @code
* \Drupal::service('update.update_hook_registry')->markFutureUpdateEquivalent(2005, '2.10')
* @endcode
* is added to ensure that a site that has run this update does not run
* MODULE_update_2005().
*
* @param int $future_update_number
* The future update number.
* @param string $future_version_string
* The version that contains the future update.
*/
public function markFutureUpdateEquivalent(int $future_update_number, string $future_version_string) : void {
[
$module,
$ran_update_number,
] = $this->determineModuleAndVersion();
if ($ran_update_number > $future_update_number) {
throw new \LogicException(sprintf('Cannot mark the update %d as an equivalent since it is less than the current update %d for the %s module ', $future_update_number, $ran_update_number, $module));
}
$data = $this->equivalentUpdates
->get($module, []);
// It does not matter if $data[$future_update_number] is already set. If two
// updates are causing the same update to be marked as equivalent then the
// latest information is the correct information to use.
$data[$future_update_number] = [
'ran_update' => $ran_update_number,
'future_version_string' => $future_version_string,
];
$this->equivalentUpdates
->set($module, $data);
}
/**
* Gets the EquivalentUpdate object for an update.
*
* @param string|null $module
* The module providing the update. If this is NULL the update to check will
* be determined from the backtrace.
* @param int|null $version
* The update to check. If this is NULL the update to check will
* be determined from the backtrace.
*
* @return \Drupal\Core\Update\EquivalentUpdate|null
* A value object with the equivalent update information or NULL if the
* update does not have an equivalent update.
*/
public function getEquivalentUpdate(?string $module = NULL, ?int $version = NULL) : ?EquivalentUpdate {
if ($module === NULL || $version === NULL) {
[
$module,
$version,
] = $this->determineModuleAndVersion();
}
$data = $this->equivalentUpdates
->get($module, []);
if (isset($data[$version]['ran_update'])) {
return new EquivalentUpdate($module, $version, $data[$version]['ran_update'], $data[$version]['future_version_string']);
}
return NULL;
}
/**
* Determines the module and update number from the stack trace.
*
* @return array<string, int>
* An array with two values. The first value is the module name and the
* second value is the update number.
*/
private function determineModuleAndVersion() : array {
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
for ($i = 0; $i < count($stack); $i++) {
if (preg_match(self::FUNC_NAME_REGEXP, $stack[$i]['function'], $matches)) {
return [
$matches['module'],
$matches['version'],
];
}
}
throw new \BadMethodCallException(__METHOD__ . ' must be called from a hook_update_N() function');
}
/**
* Removes an equivalent update.
*
* @param string $module
* The module providing the update.
* @param int $version
* The equivalent update to remove.
*
* @return bool
* TRUE if an equivalent update was removed, or FALSE if it was not.
*/
protected function deleteEquivalentUpdate(string $module, int $version) : bool {
$data = $this->equivalentUpdates
->get($module, []);
if (isset($data[$version])) {
unset($data[$version]);
if (empty($data)) {
$this->equivalentUpdates
->delete($module);
}
else {
$this->equivalentUpdates
->set($module, $data);
}
return TRUE;
}
return FALSE;
}
/**
* Returns the equivalent update information for all modules.
*
* @return array<string, array<int, array{ran_update:int, future_version_string: string}>>
* Array of modules as the keys and values as arrays of equivalent update
* information.
*/
public function getAllEquivalentUpdates() : array {
return $this->equivalentUpdates
->getAll();
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
UpdateHookRegistry | Provides module updates versions handling. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.