From a25c727d9812ccdd325e90b7a7f1cfdd5a77e462 Mon Sep 17 00:00:00 2001 From: catch <catch@35733.no-reply.drupal.org> Date: Wed, 23 Jun 2021 16:55:26 +0100 Subject: [PATCH] Issue #2124069 by voleger, andypost, martin107, damiankloip, cburschka, dawehner, kim.pepper, daffie, pcambra, ParisLiakos, Xano, longwave, MerryHamster, vacho, kostyashupenko, catch, alexpott, amateescu, Mile23, claudiu.cristea, larowlan, almaudoh, Berdir, zviryatko: Convert schema.inc to the update.update_hook_registry service (UpdateHookRegistry) --- core/core.services.yml | 8 +- core/includes/install.inc | 2 +- core/includes/schema.inc | 88 +++++----- core/includes/update.inc | 39 +++-- .../Core/Extension/ModuleExtensionList.php | 3 +- .../Drupal/Core/Extension/ModuleInstaller.php | 28 ++- .../Drupal/Core/Update/UpdateHookRegistry.php | 164 ++++++++++++++++++ .../Core/Update/UpdateHookRegistryFactory.php | 28 +++ core/lib/Drupal/Core/Updater/Module.php | 2 +- .../update/drupal-8.action-3022401.php | 12 ++ .../src/Controller/DbUpdateController.php | 2 +- .../system/src/Form/ModulesUninstallForm.php | 22 ++- core/modules/system/system.install | 14 +- .../src/Functional/Module/InstallTest.php | 6 +- .../src/Functional/System/StatusTest.php | 7 +- .../NoPreExistingSchemaUpdateTest.php | 4 +- .../UpdatePathLastRemovedTest.php | 16 +- .../UpdatePathNewDependencyTest.php | 2 +- .../UpdatePostUpdateFailingTest.php | 10 +- .../UpdateSystem/UpdatePostUpdateTest.php | 10 +- .../UpdateRemovedPostUpdateTest.php | 10 +- .../UpdateSystem/UpdateSchemaTest.php | 10 +- .../UpdateSystem/UpdateScriptTest.php | 37 ++-- .../UpdateSystem/UpdatesWith7xTest.php | 7 +- .../Kernel/Extension/ModuleHandlerTest.php | 12 +- .../Update/UpdatePathTestBaseTest.php | 10 +- .../ModuleInstallerDeprecationTest.php | 28 +++ .../Core/Extension/UpdateDeprecationTest.php | 23 +++ .../Core/Extension/UpdateDescriptionTest.php | 2 +- .../Core/Extension/UpdateSchemaTest.php | 4 +- .../Core/Update/UpdateHookRegistryTest.php | 147 ++++++++++++++++ .../Drupal/Tests/UpdatePathTestTrait.php | 10 +- 32 files changed, 608 insertions(+), 159 deletions(-) create mode 100644 core/lib/Drupal/Core/Update/UpdateHookRegistry.php create mode 100644 core/lib/Drupal/Core/Update/UpdateHookRegistryFactory.php create mode 100644 core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerDeprecationTest.php create mode 100644 core/tests/Drupal/Tests/Core/Update/UpdateHookRegistryTest.php diff --git a/core/core.services.yml b/core/core.services.yml index 6ac179594bc7..f1b8355e7937 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -527,7 +527,7 @@ services: class: Drupal\Core\Extension\ModuleInstaller tags: - { name: service_collector, tag: 'module_install.uninstall_validator', call: addUninstallValidator } - arguments: ['%app.root%', '@module_handler', '@kernel', '@database'] + arguments: ['%app.root%', '@module_handler', '@kernel', '@database', '@update.update_hook_registry'] lazy: true extension.list.module: class: Drupal\Core\Extension\ModuleExtensionList @@ -1714,6 +1714,12 @@ services: - { name: placeholder_strategy, priority: -1000 } email.validator: class: Drupal\Component\Utility\EmailValidator + update.update_hook_registry: + class: Drupal\Core\Update\UpdateHookRegistry + factory: ['@update.update_hook_registry_factory', create] + update.update_hook_registry_factory: + class: Drupal\Core\Update\UpdateHookRegistryFactory + parent: container.trait update.post_update_registry: class: Drupal\Core\Update\UpdateRegistry factory: ['@update.post_update_registry_factory', create] diff --git a/core/includes/install.inc b/core/includes/install.inc index 3983773e81d4..e68522133271 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -81,7 +81,7 @@ function drupal_load_updates() { /** @var \Drupal\Core\Extension\ModuleExtensionList $extension_list_module */ $extension_list_module = \Drupal::service('extension.list.module'); - foreach (drupal_get_installed_schema_version(NULL, FALSE, TRUE) as $module => $schema_version) { + foreach (\Drupal::service('update.update_hook_registry')->getAllInstalledVersions() as $module => $schema_version) { if ($extension_list_module->exists($module) && !$extension_list_module->checkIncompatibility($module)) { if ($schema_version > -1) { module_load_install($module); diff --git a/core/includes/schema.inc b/core/includes/schema.inc index 6026770c6a9e..62cc2024596a 100644 --- a/core/includes/schema.inc +++ b/core/includes/schema.inc @@ -5,6 +5,8 @@ * Schema API handling functions. */ +use Drupal\Core\Update\UpdateHookRegistry; + /** * @addtogroup schemaapi * @{ @@ -12,8 +14,14 @@ /** * Indicates that a module has not been installed yet. + * + * @deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use + * \Drupal\Core\Update\UpdateHookRegistry::SCHEMA_UNINSTALLED constant + * instead. + * + * @see https://www.drupal.org/node/2444417 */ -const SCHEMA_UNINSTALLED = -1; +const SCHEMA_UNINSTALLED = UpdateHookRegistry::SCHEMA_UNINSTALLED; /** * Returns an array of available schema versions for a module. @@ -24,37 +32,16 @@ * @return array|bool * If the module has updates, an array of available updates sorted by * version. Otherwise, FALSE. + * + * @deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. + * Use \Drupal\Core\Update\SchemaDataInterface::getAvailableUpdates() instead. + * + * @see https://www.drupal.org/node/2444417 + * @see \Drupal\Core\Update\UpdateHookRegistry::getAvailableUpdates() */ function drupal_get_schema_versions($module) { - $updates = &drupal_static(__FUNCTION__, NULL); - if (!isset($updates[$module])) { - $updates = []; - foreach (\Drupal::moduleHandler()->getModuleList() as $loaded_module => $filename) { - $updates[$loaded_module] = []; - } - - // Prepare regular expression to match all possible defined hook_update_N(). - $regexp = '/^(?<module>.+)_update_(?<version>\d+)$/'; - $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($regexp, $function, $matches)) { - $updates[$matches['module']][] = (int) $matches['version']; - } - } - // Ensure that updates are applied in numerical order. - foreach ($updates as &$module_updates) { - sort($module_updates, SORT_NUMERIC); - } - } - return empty($updates[$module]) ? FALSE : $updates[$module]; + @trigger_error('drupal_get_schema_versions() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Update\UpdateHookRegistry::getAvailableUpdates() instead. See https://www.drupal.org/node/2444417', E_USER_DEPRECATED); + return \Drupal::service('update.update_hook_registry')->getAvailableUpdates($module); } /** @@ -71,26 +58,23 @@ function drupal_get_schema_versions($module) { * @return string|int * The currently installed schema version, or SCHEMA_UNINSTALLED if the * module is not installed. + * + * @deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. + * Use \Drupal\Core\Update\UpdateHookRegistry::getInstalledVersion() or + * \Drupal\Core\Update\UpdateHookRegistry::getAllInstalledVersions() + * instead. + * + * @see https://www.drupal.org/node/2444417 + * @see \Drupal\Core\Update\UpdateHookRegistry::getInstalledVersion() */ function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) { - $versions = &drupal_static(__FUNCTION__, []); - - if ($reset) { - $versions = []; - } - - if (!$versions) { - if (!$versions = \Drupal::keyValue('system.schema')->getAll()) { - $versions = []; - } - } - + @trigger_error('drupal_get_installed_schema_version() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Update\UpdateHookRegistry::getInstalledVersion() or \Drupal\Core\Update\UpdateHookRegistry::getAllInstalledVersions() instead. See https://www.drupal.org/node/2444417', E_USER_DEPRECATED); + /** @var \Drupal\Core\Update\UpdateHookRegistry $service */ + $service = \Drupal::service('update.update_hook_registry'); if ($array) { - return $versions; - } - else { - return isset($versions[$module]) ? $versions[$module] : SCHEMA_UNINSTALLED; + return $service->getAllInstalledVersions(); } + return $service->getInstalledVersion((string) $module); } /** @@ -100,11 +84,17 @@ function drupal_get_installed_schema_version($module, $reset = FALSE, $array = F * A module name. * @param string $version * The new schema version. + * + * @deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. + * Use \Drupal\Core\Update\UpdateHookRegistry::setInstalledVersion() + * instead. + * + * @see https://www.drupal.org/node/2444417 + * @see \Drupal\Core\Update\UpdateHookRegistry::setInstalledVersion() */ function drupal_set_installed_schema_version($module, $version) { - \Drupal::keyValue('system.schema')->set($module, $version); - // Reset the static cache of module schema versions. - drupal_get_installed_schema_version(NULL, TRUE); + @trigger_error('drupal_set_installed_schema_version() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Update\UpdateHookRegistry::setInstalledVersion() instead. See https://www.drupal.org/node/2444417', E_USER_DEPRECATED); + \Drupal::service('update.update_hook_registry')->setInstalledVersion($module, $version); } /** diff --git a/core/includes/update.inc b/core/includes/update.inc index 77685bd5bfbf..09a6227fd659 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -57,7 +57,7 @@ function update_check_incompatibility($name, $type = 'module') { function update_system_schema_requirements() { $requirements = []; - $system_schema = drupal_get_installed_schema_version('system'); + $system_schema = \Drupal::service('update.update_hook_registry')->getInstalledVersion('system'); $requirements['minimum schema']['title'] = 'Minimum schema version'; if ($system_schema >= \Drupal::CORE_MINIMUM_SCHEMA_VERSION) { @@ -104,7 +104,9 @@ function update_check_requirements() { * https://www.drupal.org/project/drupal/issues/3130037 has been fixed. */ function _update_fix_missing_schema() { - $versions = \Drupal::keyValue('system.schema')->getAll(); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + $versions = $update_registry->getAllInstalledVersions(); $module_handler = \Drupal::moduleHandler(); $enabled_modules = $module_handler->getModuleList(); @@ -114,7 +116,7 @@ function _update_fix_missing_schema() { if (!isset($versions[$module])) { // Ensure the .install file is loaded. module_load_install($module); - $all_updates = drupal_get_schema_versions($module); + $all_updates = $update_registry->getAvailableUpdates($module); // If the schema version of a module hasn't been recorded, we cannot // know the actual schema version a module is at, because // no updates will ever have been run on the site and it was not set @@ -136,7 +138,7 @@ function _update_fix_missing_schema() { if ($last_removed = $module_handler->invoke($module, 'update_last_removed')) { $last_update = max($last_update, $last_removed); } - drupal_set_installed_schema_version($module, $last_update); + $update_registry->setInstalledVersion($module, $last_update); $args = ['%module' => $module, '%last_update_hook' => $module . '_update_' . $last_update . '()']; \Drupal::messenger()->addWarning(t('Schema information for module %module was missing from the database. You should manually review the module updates and your database to check if any updates have been skipped up to, and including, %last_update_hook.', $args)); \Drupal::logger('update')->warning('Schema information for module %module was missing from the database. You should manually review the module updates and your database to check if any updates have been skipped up to, and including, %last_update_hook.', $args); @@ -161,12 +163,11 @@ function _update_fix_missing_schema() { */ function update_set_schema($module, $schema_version) { @trigger_error('update_set_schema() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. No direct replacement is provided. See https://www.drupal.org/node/3210925', E_USER_DEPRECATED); - \Drupal::keyValue('system.schema')->set($module, $schema_version); + \Drupal::service('update.update_hook_registry')->setInstalledVersion($module, $schema_version); \Drupal::service('extension.list.profile')->reset(); \Drupal::service('extension.list.module')->reset(); \Drupal::service('extension.list.theme_engine')->reset(); \Drupal::service('extension.list.theme')->reset(); - drupal_static_reset('drupal_get_installed_schema_version'); } /** @@ -262,7 +263,7 @@ function update_do_one($module, $number, $dependency_map, &$context) { // Record the schema update if it was completed successfully. if ($context['finished'] == 1 && empty($ret['#abort'])) { - drupal_set_installed_schema_version($module, $number); + \Drupal::service('update.update_hook_registry')->setInstalledVersion($module, $number); } $context['message'] = t('Updating @module', ['@module' => $module]); @@ -351,7 +352,9 @@ function update_get_update_list() { // Make sure that the system module is first in the list of updates. $ret = ['system' => []]; - $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + $modules = $update_registry->getAllInstalledVersions(); /** @var \Drupal\Core\Extension\ExtensionList $extension_list */ $extension_list = \Drupal::service('extension.list.module'); /** @var array $installed_module_info */ @@ -359,7 +362,7 @@ function update_get_update_list() { foreach ($modules as $module => $schema_version) { // Skip uninstalled and incompatible modules. try { - if ($schema_version == SCHEMA_UNINSTALLED || $extension_list->checkIncompatibility($module)) { + if ($schema_version == $update_registry::SCHEMA_UNINSTALLED || $extension_list->checkIncompatibility($module)) { continue; } } @@ -393,8 +396,8 @@ function update_get_update_list() { continue; } // Otherwise, get the list of updates defined by this module. - $updates = drupal_get_schema_versions($module); - if ($updates !== FALSE) { + $updates = $update_registry->getAvailableUpdates($module); + if ($updates) { foreach ($updates as $update) { if ($update == \Drupal::CORE_MINIMUM_SCHEMA_VERSION) { $ret[$module]['warning'] = '<em>' . $module . '</em> module cannot be updated. It contains an update numbered as ' . \Drupal::CORE_MINIMUM_SCHEMA_VERSION . ' which is reserved for the earliest installation of a module in Drupal ' . \Drupal::CORE_COMPATIBILITY . ', before any updates. In order to update <em>' . $module . '</em> module, you will need to install a version of the module with valid updates.'; @@ -527,10 +530,12 @@ function update_get_update_function_list($starting_updates) { // Go through each module and find all updates that we need (including the // first update that was requested and any updates that run after it). $update_functions = []; + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); foreach ($starting_updates as $module => $version) { $update_functions[$module] = []; - $updates = drupal_get_schema_versions($module); - if ($updates !== FALSE) { + $updates = $update_registry->getAvailableUpdates($module); + if ($updates) { $max_version = max($updates); if ($version <= $max_version) { foreach ($updates as $update) { @@ -659,7 +664,7 @@ function update_is_missing($module, $number, $update_functions) { * performed; FALSE otherwise. */ function update_already_performed($module, $number) { - return $number <= drupal_get_installed_schema_version($module); + return $number <= \Drupal::service('update.update_hook_registry')->getInstalledVersion($module); } /** @@ -681,9 +686,11 @@ function update_retrieve_dependencies() { $return = []; /** @var \Drupal\Core\Extension\ModuleExtensionList */ $extension_list = \Drupal::service('extension.list.module'); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); // Get a list of installed modules, arranged so that we invoke their hooks in // the same order that \Drupal::moduleHandler()->invokeAll() does. - foreach (\Drupal::keyValue('system.schema')->getAll() as $module => $schema) { + foreach ($update_registry->getAllInstalledVersions() as $module => $schema) { // Skip modules that are entirely missing from the filesystem here, since // module_load_install() will call trigger_error() if invoked on a module // that doesn't exist. There's no way to catch() that, so avoid it entirely. @@ -691,7 +698,7 @@ function update_retrieve_dependencies() { // store for modules that have been removed from a site without first being // cleanly uninstalled. We don't care here if the module has been installed // or not, since we'll filter those out in update_get_update_list(). - if ($schema == SCHEMA_UNINSTALLED || !$extension_list->exists($module)) { + if ($schema == $update_registry::SCHEMA_UNINSTALLED || !$extension_list->exists($module)) { // Nothing to upgrade. continue; } diff --git a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php index c05d892b24bf..ab4be3869846 100644 --- a/core/lib/Drupal/Core/Extension/ModuleExtensionList.php +++ b/core/lib/Drupal/Core/Extension/ModuleExtensionList.php @@ -6,6 +6,7 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\Update\UpdateHookRegistry; /** * Provides a list of available modules. @@ -163,7 +164,7 @@ protected function doList() { foreach ($extensions as $name => $module) { $module->weight = isset($installed_modules[$name]) ? $installed_modules[$name] : 0; $module->status = (int) isset($installed_modules[$name]); - $module->schema_version = SCHEMA_UNINSTALLED; + $module->schema_version = UpdateHookRegistry::SCHEMA_UNINSTALLED; } $extensions = $this->moduleHandler->buildModuleDependencies($extensions); diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php index 55d1385cb8df..83cf8bed678f 100644 --- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php @@ -11,6 +11,7 @@ use Drupal\Core\Extension\Exception\ObsoleteExtensionException; use Drupal\Core\Installer\InstallerKernel; use Drupal\Core\Serialization\Yaml; +use Drupal\Core\Update\UpdateHookRegistry; /** * Default implementation of the module installer. @@ -53,6 +54,13 @@ class ModuleInstaller implements ModuleInstallerInterface { */ protected $connection; + /** + * The update registry service. + * + * @var \Drupal\Core\Update\UpdateHookRegistry + */ + protected $updateRegistry; + /** * The uninstall validators. * @@ -71,11 +79,13 @@ class ModuleInstaller implements ModuleInstallerInterface { * The drupal kernel. * @param \Drupal\Core\Database\Connection $connection * The database connection. + * @param \Drupal\Core\Update\UpdateHookRegistry|null $update_registry + * (Optional) The update registry service. * * @see \Drupal\Core\DrupalKernel * @see \Drupal\Core\CoreServiceProvider */ - public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel, Connection $connection = NULL) { + public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel, Connection $connection = NULL, UpdateHookRegistry $update_registry = NULL) { $this->root = $root; $this->moduleHandler = $module_handler; $this->kernel = $kernel; @@ -84,6 +94,11 @@ public function __construct($root, ModuleHandlerInterface $module_handler, Drupa $connection = \Drupal::service('database'); } $this->connection = $connection; + if (!$update_registry) { + @trigger_error('Calling ' . __METHOD__ . '() without the $update_registry argument is deprecated in drupal:9.3.0 and $update_registry argument will be required in drupal:10.0.0. See https://www.drupal.org/node/2124069', E_USER_DEPRECATED); + $update_registry = \Drupal::service('update.update_hook_registry'); + } + $this->updateRegistry = $update_registry; } /** @@ -254,7 +269,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) { // Set the schema version to the number of the last update provided by // the module, or the minimum core schema version. $version = \Drupal::CORE_MINIMUM_SCHEMA_VERSION; - $versions = drupal_get_schema_versions($module); + $versions = $this->updateRegistry->getAvailableUpdates($module); if ($versions) { $version = max(max($versions), $version); } @@ -314,7 +329,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) { if ($last_removed = $this->moduleHandler->invoke($module, 'update_last_removed')) { $version = max($version, $last_removed); } - drupal_set_installed_schema_version($module, $version); + $this->updateRegistry->setInstalledVersion($module, $version); // Ensure that all post_update functions are registered already. This // should include existing post-updates, as well as any specified as @@ -525,8 +540,9 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) { \Drupal::logger('system')->info('%module module uninstalled.', ['%module' => $module]); - $schema_store = \Drupal::keyValue('system.schema'); - $schema_store->delete($module); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + $update_registry->deleteInstalledVersion($module); /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */ $post_update_registry = \Drupal::service('update.post_update_registry'); @@ -537,7 +553,6 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) { // fastCGI which executes ::destruct() after the Module uninstallation page // was sent already. \Drupal::service('router.builder')->rebuild(); - drupal_get_installed_schema_version(NULL, TRUE); // Let other modules react. $this->moduleHandler->invokeAll('modules_uninstalled', [$module_list, $sync_status]); @@ -605,6 +620,7 @@ protected function updateKernel($module_filenames) { $container = $this->kernel->getContainer(); $this->moduleHandler = $container->get('module_handler'); $this->connection = $container->get('database'); + $this->updateRegistry = $container->get('update.update_hook_registry'); } /** diff --git a/core/lib/Drupal/Core/Update/UpdateHookRegistry.php b/core/lib/Drupal/Core/Update/UpdateHookRegistry.php new file mode 100644 index 000000000000..db5a8c43d6e3 --- /dev/null +++ b/core/lib/Drupal/Core/Update/UpdateHookRegistry.php @@ -0,0 +1,164 @@ +<?php + +namespace Drupal\Core\Update; + +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; + + /** + * A list of enabled modules. + * + * @var string[] + */ + protected $enabledModules; + + /** + * The key value storage. + * + * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface + */ + protected $keyValue; + + /** + * 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 UpdateRegistry. + * + * @param string[] $enabled_modules + * A list of enabled modules. + * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $key_value + * The key value store. + */ + public function __construct(array $enabled_modules, KeyValueStoreInterface $key_value) { + $this->enabledModules = $enabled_modules; + $this->keyValue = $key_value; + } + + /** + * 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] = []; + } + + // Prepare regular expression to match all possible defined + // hook_update_N(). + $regexp = '/^(?<module>.+)_update_(?<version>\d+)$/'; + $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($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); + 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); + } + + /** + * 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(); + } + +} diff --git a/core/lib/Drupal/Core/Update/UpdateHookRegistryFactory.php b/core/lib/Drupal/Core/Update/UpdateHookRegistryFactory.php new file mode 100644 index 000000000000..45ac2b8d7c7d --- /dev/null +++ b/core/lib/Drupal/Core/Update/UpdateHookRegistryFactory.php @@ -0,0 +1,28 @@ +<?php + +namespace Drupal\Core\Update; + +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerAwareTrait; + +/** + * Service factory for the versioning update registry. + */ +class UpdateHookRegistryFactory implements ContainerAwareInterface { + + use ContainerAwareTrait; + + /** + * Creates a new UpdateHookRegistry instance. + * + * @return \Drupal\Core\Update\UpdateHookRegistry + * The update registry instance. + */ + public function create() { + return new UpdateHookRegistry( + array_keys($this->container->get('module_handler')->getModuleList()), + $this->container->get('keyvalue')->get('system.schema') + ); + } + +} diff --git a/core/lib/Drupal/Core/Updater/Module.php b/core/lib/Drupal/Core/Updater/Module.php index 69330cef9097..a50eda12bf26 100644 --- a/core/lib/Drupal/Core/Updater/Module.php +++ b/core/lib/Drupal/Core/Updater/Module.php @@ -89,7 +89,7 @@ public function getSchemaUpdates() { } module_load_include('install', $this->name); - if (!$updates = drupal_get_schema_versions($this->name)) { + if (!\Drupal::service('update.update_hook_registry')->getAvailableUpdates($this->name)) { return []; } $modules_with_updates = update_get_update_list(); diff --git a/core/modules/action/tests/fixtures/update/drupal-8.action-3022401.php b/core/modules/action/tests/fixtures/update/drupal-8.action-3022401.php index bfebb360eaa8..7c73a36bb2ed 100644 --- a/core/modules/action/tests/fixtures/update/drupal-8.action-3022401.php +++ b/core/modules/action/tests/fixtures/update/drupal-8.action-3022401.php @@ -39,3 +39,15 @@ ]) ->condition('name', 'core.extension') ->execute(); +$connection->insert('key_value') + ->fields([ + 'collection', + 'name', + 'value', + ]) + ->values([ + 'collection' => 'system.schema', + 'name' => 'action', + 'value' => serialize(8000), + ]) + ->execute(); diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php index 05af92706f8c..1732660b24cf 100644 --- a/core/modules/system/src/Controller/DbUpdateController.php +++ b/core/modules/system/src/Controller/DbUpdateController.php @@ -605,7 +605,7 @@ protected function triggerBatch(Request $request) { // correct place. (The updates are already sorted, so we can simply base // this on the first one we come across in the above foreach loop.) if (isset($start[$update['module']])) { - drupal_set_installed_schema_version($update['module'], $update['number'] - 1); + \Drupal::service('update.update_hook_registry')->setInstalledVersion($update['module'], $update['number'] - 1); unset($start[$update['module']]); } $operations[] = ['update_do_one', [$update['module'], $update['number'], $dependency_map[$function]]]; diff --git a/core/modules/system/src/Form/ModulesUninstallForm.php b/core/modules/system/src/Form/ModulesUninstallForm.php index 52f378e70941..3d2c70b61c87 100644 --- a/core/modules/system/src/Form/ModulesUninstallForm.php +++ b/core/modules/system/src/Form/ModulesUninstallForm.php @@ -8,6 +8,7 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface; +use Drupal\Core\Update\UpdateHookRegistry; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -45,6 +46,13 @@ class ModulesUninstallForm extends FormBase { */ protected $moduleExtensionList; + /** + * The update registry service. + * + * @var \Drupal\Core\Update\UpdateHookRegistry + */ + protected $updateRegistry; + /** * {@inheritdoc} */ @@ -53,7 +61,8 @@ public static function create(ContainerInterface $container) { $container->get('module_handler'), $container->get('module_installer'), $container->get('keyvalue.expirable')->get('modules_uninstall'), - $container->get('extension.list.module') + $container->get('extension.list.module'), + $container->get('update.update_hook_registry') ); } @@ -68,12 +77,19 @@ public static function create(ContainerInterface $container) { * The key value expirable factory. * @param \Drupal\Core\Extension\ModuleExtensionList $extension_list_module * The module extension list. + * @param \Drupal\Core\Update\UpdateHookRegistry|null $versioning_update_registry + * Versioning update registry service. */ - public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, ModuleExtensionList $extension_list_module) { + public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, ModuleExtensionList $extension_list_module, UpdateHookRegistry $versioning_update_registry = NULL) { $this->moduleExtensionList = $extension_list_module; $this->moduleHandler = $module_handler; $this->moduleInstaller = $module_installer; $this->keyValueExpirable = $key_value_expirable; + if ($versioning_update_registry === NULL) { + @trigger_error('The update.update_hook_registry service must be passed to ' . __NAMESPACE__ . '\ModulesUninstallForm::__construct(). It was added in drupal:9.3.0 and will be required before drupal:10.0.0.', E_USER_DEPRECATED); + $versioning_update_registry = \Drupal::service('update.update_hook_registry'); + } + $this->updateRegistry = $versioning_update_registry; } /** @@ -153,7 +169,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { // All modules which depend on this one must be uninstalled first, before // we can allow this module to be uninstalled. foreach (array_keys($module->required_by) as $dependent) { - if (drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED) { + if ($this->updateRegistry->getInstalledVersion($dependent) !== $this->updateRegistry::SCHEMA_UNINSTALLED) { $form['modules'][$module->getName()]['#required_by'][] = $dependent; $form['uninstall'][$module->getName()]['#disabled'] = TRUE; } diff --git a/core/modules/system/system.install b/core/modules/system/system.install index cf83bd4d044e..f0a4f317be3f 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -789,10 +789,12 @@ function system_requirements($phase) { // Check installed modules. $has_pending_updates = FALSE; + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); foreach (\Drupal::moduleHandler()->getModuleList() as $module => $filename) { - $updates = drupal_get_schema_versions($module); - if ($updates !== FALSE) { - $default = drupal_get_installed_schema_version($module); + $updates = $update_registry->getAvailableUpdates($module); + if ($updates) { + $default = $update_registry->getInstalledVersion($module); if (max($updates) > $default) { $has_pending_updates = TRUE; break; @@ -1224,17 +1226,19 @@ function system_requirements($phase) { // one that was last removed. if ($phase == 'update') { $module_handler = \Drupal::moduleHandler(); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); $module_list = []; foreach ($module_handler->getImplementations('update_last_removed') as $module) { $last_removed = $module_handler->invoke($module, 'update_last_removed'); - if ($last_removed && $last_removed > drupal_get_installed_schema_version($module)) { + if ($last_removed && $last_removed > $update_registry->getInstalledVersion($module)) { /** @var \Drupal\Core\Extension\Extension $module_info */ $module_info = \Drupal::service('extension.list.module')->get($module); $module_list[$module] = [ 'name' => $module_info->info['name'], 'last_removed' => $last_removed, - 'installed_version' => drupal_get_installed_schema_version($module), + 'installed_version' => $update_registry->getInstalledVersion($module), ]; } } diff --git a/core/modules/system/tests/src/Functional/Module/InstallTest.php b/core/modules/system/tests/src/Functional/Module/InstallTest.php index ab9cacb222d7..0bc323ea291b 100644 --- a/core/modules/system/tests/src/Functional/Module/InstallTest.php +++ b/core/modules/system/tests/src/Functional/Module/InstallTest.php @@ -51,9 +51,11 @@ public function testEnableUserTwice() { * Tests recorded schema versions of early installed modules in the installer. */ public function testRequiredModuleSchemaVersions() { - $version = drupal_get_installed_schema_version('system', TRUE); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + $version = $update_registry->getInstalledVersion('system'); $this->assertGreaterThan(0, $version); - $version = drupal_get_installed_schema_version('user', TRUE); + $version = $update_registry->getInstalledVersion('user'); $this->assertGreaterThan(0, $version); $post_update_key_value = \Drupal::keyValue('post_update'); diff --git a/core/modules/system/tests/src/Functional/System/StatusTest.php b/core/modules/system/tests/src/Functional/System/StatusTest.php index cab94ed7ccd4..717c4aa56e16 100644 --- a/core/modules/system/tests/src/Functional/System/StatusTest.php +++ b/core/modules/system/tests/src/Functional/System/StatusTest.php @@ -65,14 +65,17 @@ public function testStatusPage() { // The setting config_sync_directory is not properly formed. $this->assertRaw(t("Your %file file must define the %setting setting", ['%file' => $this->siteDirectory . '/settings.php', '%setting' => "\$settings['config_sync_directory']"])); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + // Set the schema version of update_test_postupdate to a lower version, so // update_test_postupdate_update_8001() needs to be executed. - drupal_set_installed_schema_version('update_test_postupdate', 8000); + $update_registry->setInstalledVersion('update_test_postupdate', 8000); $this->drupalGet('admin/reports/status'); $this->assertSession()->pageTextContains('Out of date'); // Now cleanup the executed post update functions. - drupal_set_installed_schema_version('update_test_postupdate', 8001); + $update_registry->setInstalledVersion('update_test_postupdate', 8001); /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */ $post_update_registry = \Drupal::service('update.post_update_registry'); $post_update_registry->filterOutInvokedUpdatesByModule('update_test_postupdate'); diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/NoPreExistingSchemaUpdateTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/NoPreExistingSchemaUpdateTest.php index 0f889155d488..097341901c8f 100644 --- a/core/modules/system/tests/src/Functional/UpdateSystem/NoPreExistingSchemaUpdateTest.php +++ b/core/modules/system/tests/src/Functional/UpdateSystem/NoPreExistingSchemaUpdateTest.php @@ -45,7 +45,7 @@ protected function setUp(): void { * Tests the system module updates with no dependencies installed. */ public function testNoPreExistingSchema() { - $schema = \Drupal::keyValue('system.schema')->getAll(); + $schema = \Drupal::service('update.update_hook_registry')->getAllInstalledVersions(); $this->assertArrayNotHasKey('update_test_no_preexisting', $schema); $this->assertFalse(\Drupal::state()->get('update_test_no_preexisting_update_8001', FALSE)); @@ -65,7 +65,7 @@ public function testNoPreExistingSchema() { $this->drupalGet($update_url); $this->updateRequirementsProblem(); - $schema = \Drupal::keyValue('system.schema')->getAll(); + $schema = \Drupal::service('update.update_hook_registry')->getAllInstalledVersions(); $this->assertArrayHasKey('update_test_no_preexisting', $schema); $this->assertEquals('8001', $schema['update_test_no_preexisting']); // The schema version has been fixed, but the update was never run. diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathLastRemovedTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathLastRemovedTest.php index 78d08581921c..40d5d1e57324 100644 --- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathLastRemovedTest.php +++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathLastRemovedTest.php @@ -55,8 +55,11 @@ protected function setUp(): void { * Tests that a module with a too old schema version can not be updated. */ public function testLastRemovedVersion() { - drupal_set_installed_schema_version('update_test_last_removed', 8000); - drupal_set_installed_schema_version('system', 8804); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + + $update_registry->setInstalledVersion('update_test_last_removed', 8000); + $update_registry->setInstalledVersion('system', 8804); // Access the update page with a schema version that is too old for system // and the test module, only the generic core message should be shown. @@ -72,7 +75,7 @@ public function testLastRemovedVersion() { // Update the installed version of system and then assert that now, // the test module is shown instead. - drupal_set_installed_schema_version('system', 8805); + $update_registry->setInstalledVersion('system', 8805); $this->drupalGet($this->updateUrl); $assert_session->pageTextNotContains('The version of Drupal you are trying to update from is too old'); @@ -83,11 +86,12 @@ public function testLastRemovedVersion() { // Set the expected schema version for the node and test module, updates are // successful now. - drupal_set_installed_schema_version('update_test_last_removed', 8002); + $update_registry->setInstalledVersion('update_test_last_removed', 8002); $this->runUpdates(); - $this->assertEquals(8003, drupal_get_installed_schema_version('update_test_last_removed')); - + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + $this->assertEquals(8003, $update_registry->getInstalledVersion('update_test_last_removed')); } } diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathNewDependencyTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathNewDependencyTest.php index d126d91609ab..8a2233c8063b 100644 --- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathNewDependencyTest.php +++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePathNewDependencyTest.php @@ -31,7 +31,7 @@ public function testUpdateNewDependency() { ->set('module.new_dependency_test', 0) ->set('module', module_config_sort($extension_config->get('module'))) ->save(TRUE); - drupal_set_installed_schema_version('new_dependency_test', \Drupal::CORE_MINIMUM_SCHEMA_VERSION); + \Drupal::service('update.update_hook_registry')->setInstalledVersion('new_dependency_test', \Drupal::CORE_MINIMUM_SCHEMA_VERSION); // Rebuild the container and test that the service with the optional unmet // dependency is still available while the ones that fail are not. diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePostUpdateFailingTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePostUpdateFailingTest.php index d42df7d9a919..d5b678dc160d 100644 --- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePostUpdateFailingTest.php +++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePostUpdateFailingTest.php @@ -27,15 +27,7 @@ protected function setUp(): void { $connection = Database::getConnection(); // Set the schema version. - $connection->merge('key_value') - ->condition('collection', 'system.schema') - ->condition('name', 'update_test_failing') - ->fields([ - 'collection' => 'system.schema', - 'name' => 'update_test_failing', - 'value' => 'i:8000;', - ]) - ->execute(); + \Drupal::service('update.update_hook_registry')->setInstalledVersion('update_test_failing', 8000); // Update core.extension. $extensions = $connection->select('config') diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePostUpdateTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePostUpdateTest.php index bebc7247a7a2..25c5a89b15f1 100644 --- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePostUpdateTest.php +++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatePostUpdateTest.php @@ -28,15 +28,7 @@ protected function setUp(): void { $connection = Database::getConnection(); // Set the schema version. - $connection->merge('key_value') - ->condition('collection', 'system.schema') - ->condition('name', 'update_test_postupdate') - ->fields([ - 'collection' => 'system.schema', - 'name' => 'update_test_postupdate', - 'value' => 'i:8000;', - ]) - ->execute(); + \Drupal::service('update.update_hook_registry')->setInstalledVersion('update_test_postupdate', 8000); // Update core.extension. $extensions = $connection->select('config') diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdateRemovedPostUpdateTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdateRemovedPostUpdateTest.php index c58aa5303b6c..8809c9514756 100644 --- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdateRemovedPostUpdateTest.php +++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdateRemovedPostUpdateTest.php @@ -28,15 +28,7 @@ protected function setUp(): void { $connection = Database::getConnection(); // Set the schema version. - $connection->merge('key_value') - ->condition('collection', 'system.schema') - ->condition('name', 'update_test_postupdate') - ->fields([ - 'collection' => 'system.schema', - 'name' => 'update_test_postupdate', - 'value' => 'i:8000;', - ]) - ->execute(); + \Drupal::service('update.update_hook_registry')->setInstalledVersion('update_test_postupdate', 8000); // Update core.extension. $extensions = $connection->select('config') diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdateSchemaTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdateSchemaTest.php index 08f60f71964c..c39a8d99a1ef 100644 --- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdateSchemaTest.php +++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdateSchemaTest.php @@ -58,8 +58,11 @@ protected function setUp(): void { public function testUpdateHooks() { $connection = Database::getConnection(); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + // Verify that the 8000 schema is in place. - $this->assertEquals(8000, drupal_get_installed_schema_version('update_test_schema')); + $this->assertEquals(8000, $update_registry->getInstalledVersion('update_test_schema')); $this->assertFalse($connection->schema()->indexExists('update_test_schema_table', 'test'), 'Version 8000 of the update_test_schema module is installed.'); // Increment the schema version. @@ -75,7 +78,10 @@ public function testUpdateHooks() { $this->checkForMetaRefresh(); // Ensure schema has changed. - $this->assertEquals(8001, drupal_get_installed_schema_version('update_test_schema', TRUE)); + $this->resetAll(); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + $this->assertEquals(8001, $update_registry->getInstalledVersion('update_test_schema')); // Ensure the index was added for column a. $this->assertTrue($connection->schema()->indexExists('update_test_schema_table', 'test'), 'Version 8001 of the update_test_schema module is installed.'); } diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php index 401c01e8f73b..76d14154edab 100644 --- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php +++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php @@ -142,7 +142,9 @@ public function testRequirements() { // successfully. $this->drupalLogin($this->updateUser); $update_script_test_config->set('requirement_type', REQUIREMENT_WARNING)->save(); - drupal_set_installed_schema_version('update_script_test', drupal_get_installed_schema_version('update_script_test') - 1); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + $update_registry->setInstalledVersion('update_script_test', $update_registry->getInstalledVersion('update_script_test') - 1); $this->drupalGet($this->updateUrl, ['external' => TRUE]); $this->assertSession()->pageTextContains('This is a requirements warning provided by the update_script_test module.'); $this->clickLink('try again'); @@ -410,7 +412,7 @@ public function testOrphanedSchemaEntries() { // nonexistent module. This replicates what would happen if you had a module // installed and then completely remove it from the filesystem and clear it // out of the core.extension config list without uninstalling it cleanly. - \Drupal::keyValue('system.schema')->set('my_already_removed_module', 8000); + \Drupal::service('update.update_hook_registry')->setInstalledVersion('my_already_removed_module', 8000); // Visit update.php and make sure we can click through to the 'No pending // updates' page without errors. @@ -424,8 +426,8 @@ public function testOrphanedSchemaEntries() { // Try again with another orphaned entry, this time for a test module that // does exist in the filesystem. - \Drupal::keyValue('system.schema')->delete('my_already_removed_module'); - \Drupal::keyValue('system.schema')->set('update_test_0', 8000); + \Drupal::service('update.update_hook_registry')->deleteInstalledVersion('my_already_removed_module'); + \Drupal::service('update.update_hook_registry')->setInstalledVersion('update_test_0', 8000); $this->drupalGet($this->updateUrl, ['external' => TRUE]); $this->updateRequirementsProblem(); $this->clickLink(t('Continue')); @@ -435,7 +437,7 @@ public function testOrphanedSchemaEntries() { $this->assertSession()->elementTextEquals('xpath', '//div[@aria-label="Warning message"]', 'Warning message Module update_test_0 has an entry in the system.schema key/value storage, but is not installed. More information about this error.'); // Finally, try with both kinds of orphans and make sure we get both warnings. - \Drupal::keyValue('system.schema')->set('my_already_removed_module', 8000); + \Drupal::service('update.update_hook_registry')->setInstalledVersion('my_already_removed_module', 8000); $this->drupalGet($this->updateUrl, ['external' => TRUE]); $this->updateRequirementsProblem(); $this->clickLink(t('Continue')); @@ -538,12 +540,15 @@ public function testSuccessfulUpdateFunctionality() { $this->assertEquals($initial_maintenance_mode, $final_maintenance_mode, 'Maintenance mode should not have changed after database updates.'); // Reset the static cache to ensure we have the most current setting. - $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE); + $this->resetAll(); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + $schema_version = $update_registry->getInstalledVersion('update_script_test'); $this->assertEquals(8001, $schema_version, 'update_script_test schema version is 8001 after updating.'); // Set the installed schema version to one less than the current update. - drupal_set_installed_schema_version('update_script_test', $schema_version - 1); - $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE); + $update_registry->setInstalledVersion('update_script_test', $schema_version - 1); + $schema_version = $update_registry->getInstalledVersion('update_script_test'); $this->assertEquals(8000, $schema_version, 'update_script_test schema version overridden to 8000.'); // Click through update.php with 'access administration pages' and @@ -603,12 +608,14 @@ public function testSuccessfulMultilingualUpdateFunctionality() { $config->save(); // Reset the static cache to ensure we have the most current setting. - $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + $schema_version = $update_registry->getInstalledVersion('update_script_test'); $this->assertEquals(8001, $schema_version, 'update_script_test schema version is 8001 after updating.'); // Set the installed schema version to one less than the current update. - drupal_set_installed_schema_version('update_script_test', $schema_version - 1); - $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE); + $update_registry->setInstalledVersion('update_script_test', $schema_version - 1); + $schema_version = $update_registry->getInstalledVersion('update_script_test'); $this->assertEquals(8000, $schema_version, 'update_script_test schema version overridden to 8000.'); // Create admin user. @@ -672,12 +679,14 @@ public function testMaintenanceModeLink() { * Helper function to run updates via the browser. */ protected function runUpdates($maintenance_mode) { - $schema_version = drupal_get_installed_schema_version('update_script_test'); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + $schema_version = $update_registry->getInstalledVersion('update_script_test'); $this->assertEquals(8001, $schema_version, 'update_script_test is initially installed with schema version 8001.'); // Set the installed schema version to one less than the current update. - drupal_set_installed_schema_version('update_script_test', $schema_version - 1); - $schema_version = drupal_get_installed_schema_version('update_script_test', TRUE); + $update_registry->setInstalledVersion('update_script_test', $schema_version - 1); + $schema_version = $update_registry->getInstalledVersion('update_script_test'); $this->assertEquals(8000, $schema_version, 'update_script_test schema version overridden to 8000.'); // Click through update.php with 'administer software updates' permission. diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatesWith7xTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatesWith7xTest.php index 513bbfb7cbcb..37dace4d3b23 100644 --- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdatesWith7xTest.php +++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdatesWith7xTest.php @@ -49,13 +49,16 @@ protected function setUp(): void { } public function testWith7x() { + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + // Ensure that the minimum schema version is 8000, despite 7200 update // hooks and a 7XXX hook_update_last_removed(). - $this->assertEquals(8000, drupal_get_installed_schema_version('update_test_with_7x')); + $this->assertEquals(8000, $update_registry->getInstalledVersion('update_test_with_7x')); // Try to manually set the schema version to 7110 and ensure that no // updates are allowed. - drupal_set_installed_schema_version('update_test_with_7x', 7110); + $update_registry->setInstalledVersion('update_test_with_7x', 7110); // Click through update.php with 'administer software updates' permission. $this->drupalLogin($this->updateUser); diff --git a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php index d3411dd881e0..9d588a23e171 100644 --- a/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php +++ b/core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php @@ -125,8 +125,10 @@ public function testDependencyResolution() { $result = $this->moduleInstaller()->uninstall(['config', 'help', 'color']); $this->assertTrue($result, 'ModuleInstaller::uninstall() returned TRUE.'); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); foreach (['color', 'config', 'help'] as $module) { - $this->assertEquals(SCHEMA_UNINSTALLED, drupal_get_installed_schema_version($module), "{$module} module was uninstalled."); + $this->assertEquals($update_registry::SCHEMA_UNINSTALLED, $update_registry->getInstalledVersion($module), "{$module} module was uninstalled."); } $uninstalled_modules = \Drupal::state()->get('module_test.uninstall_order', []); $this->assertEquals(['color', 'config', 'help'], $uninstalled_modules, 'Modules were uninstalled in the correct order.'); @@ -176,10 +178,12 @@ public function testUninstallProfileDependency() { $this->assertTrue($this->moduleHandler()->moduleExists($dependency)); // Uninstall the profile module that is not a dependent. + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); $result = $this->moduleInstaller()->uninstall([$non_dependency]); $this->assertTrue($result, 'ModuleInstaller::uninstall() returns TRUE.'); $this->assertFalse($this->moduleHandler()->moduleExists($non_dependency)); - $this->assertEquals(SCHEMA_UNINSTALLED, drupal_get_installed_schema_version($non_dependency), "$non_dependency module was uninstalled."); + $this->assertEquals($update_registry::SCHEMA_UNINSTALLED, $update_registry->getInstalledVersion($non_dependency), "$non_dependency module was uninstalled."); // Verify that the installation profile itself was not uninstalled. $uninstalled_modules = \Drupal::state()->get('module_test.uninstall_order', []); @@ -272,9 +276,11 @@ public function testUninstallContentDependency() { // Deleting the entity. $entity->delete(); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); $result = $this->moduleInstaller()->uninstall(['help']); $this->assertTrue($result, 'ModuleInstaller::uninstall() returns TRUE.'); - $this->assertEquals(SCHEMA_UNINSTALLED, drupal_get_installed_schema_version('entity_test'), "entity_test module was uninstalled."); + $this->assertEquals($update_registry::SCHEMA_UNINSTALLED, $update_registry->getInstalledVersion('entity_test'), "entity_test module was uninstalled."); } /** diff --git a/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBaseTest.php b/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBaseTest.php index 925e18922c38..002749547e8c 100644 --- a/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBaseTest.php +++ b/core/tests/Drupal/FunctionalTests/Update/UpdatePathTestBaseTest.php @@ -34,8 +34,10 @@ public function testDatabaseLoaded() { // Set a value in the cache to prove caches are cleared. \Drupal::service('cache.default')->set(__CLASS__, 'Test'); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); foreach (['user' => 8100, 'node' => 8700, 'system' => 8901, 'update_test_schema' => 8000] as $module => $schema) { - $this->assertEquals($schema, drupal_get_installed_schema_version($module), new FormattableMarkup('Module @module schema is @schema', ['@module' => $module, '@schema' => $schema])); + $this->assertEquals($schema, $update_registry->getInstalledVersion($module), new FormattableMarkup('Module @module schema is @schema', ['@module' => $module, '@schema' => $schema])); } // Ensure that all {router} entries can be unserialized. If they cannot be @@ -100,8 +102,10 @@ public function testUpdateHookN() { $this->assertEquals([], $container_cannot_be_saved_messages); // Ensure schema has changed. - $this->assertEquals(8001, drupal_get_installed_schema_version('update_test_schema', TRUE)); - $this->assertEquals(8001, drupal_get_installed_schema_version('update_test_semver_update_n', TRUE)); + /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ + $update_registry = \Drupal::service('update.update_hook_registry'); + $this->assertEquals(8001, $update_registry->getInstalledVersion('update_test_schema')); + $this->assertEquals(8001, $update_registry->getInstalledVersion('update_test_semver_update_n')); // Ensure the index was added for column a. $this->assertTrue($connection->schema()->indexExists('update_test_schema_table', 'test'), 'Version 8001 of the update_test_schema module is installed.'); // Ensure update_test_semver_update_n_update_8001 was run. diff --git a/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerDeprecationTest.php b/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerDeprecationTest.php new file mode 100644 index 000000000000..2555fb518dcd --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleInstallerDeprecationTest.php @@ -0,0 +1,28 @@ +<?php + +namespace Drupal\KernelTests\Core\Extension; + +use Drupal\Core\DrupalKernelInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Extension\ModuleInstaller; +use Drupal\KernelTests\KernelTestBase; + +/** + * @group legacy + * @group extension + * @coversDefaultClass \Drupal\Core\Extension\ModuleInstaller + */ +class ModuleInstallerDeprecationTest extends KernelTestBase { + + /** + * @covers ::__construct + */ + public function testConstructorDeprecation() { + $this->expectDeprecation('Calling ' . ModuleInstaller::class . '::__construct() without the $update_registry argument is deprecated in drupal:9.3.0 and $update_registry argument will be required in drupal:10.0.0. See https://www.drupal.org/node/2124069'); + $root = ''; + $module_handler = $this->prophesize(ModuleHandlerInterface::class); + $kernel = $this->prophesize(DrupalKernelInterface::class); + $this->assertNotNull(new ModuleInstaller($root, $module_handler->reveal(), $kernel->reveal())); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Extension/UpdateDeprecationTest.php b/core/tests/Drupal/KernelTests/Core/Extension/UpdateDeprecationTest.php index 6e6f8584a4ea..21f739d4a970 100644 --- a/core/tests/Drupal/KernelTests/Core/Extension/UpdateDeprecationTest.php +++ b/core/tests/Drupal/KernelTests/Core/Extension/UpdateDeprecationTest.php @@ -43,4 +43,27 @@ public function testUpdateSetSchema() { $this->assertEquals(8003, \Drupal::keyValue('system.schema')->get('update_test_schema')); } + /** + * Deprecation testing for drupal_get_schema_versions function. + * + * @see drupal_get_schema_versions() + */ + public function testDrupalGetSchemaVersionsLegacyTest() { + $this->expectDeprecation('drupal_get_schema_versions() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Update\UpdateHookRegistry::getAvailableUpdates() instead. See https://www.drupal.org/node/2444417'); + $this->assertEmpty(drupal_get_schema_versions('update_test_schema')); + } + + /** + * Deprecation testing for drupal installed schema version functions. + * + * @see drupal_get_installed_schema_version() + * @see drupal_set_installed_schema_version() + */ + public function testDrupalGetInstalledSchemaVersion() { + $this->expectDeprecation('drupal_get_installed_schema_version() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Update\UpdateHookRegistry::getInstalledVersion() or \Drupal\Core\Update\UpdateHookRegistry::getAllInstalledVersions() instead. See https://www.drupal.org/node/2444417'); + $this->assertIsArray(drupal_get_installed_schema_version(NULL, TRUE, TRUE)); + $this->expectDeprecation('drupal_set_installed_schema_version() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use \Drupal\Core\Update\UpdateHookRegistry::setInstalledVersion() instead. See https://www.drupal.org/node/2444417'); + drupal_set_installed_schema_version('system', 8001); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Extension/UpdateDescriptionTest.php b/core/tests/Drupal/KernelTests/Core/Extension/UpdateDescriptionTest.php index 371a5f7f03bb..0c12d3ac197c 100644 --- a/core/tests/Drupal/KernelTests/Core/Extension/UpdateDescriptionTest.php +++ b/core/tests/Drupal/KernelTests/Core/Extension/UpdateDescriptionTest.php @@ -23,7 +23,7 @@ class UpdateDescriptionTest extends KernelTestBase { */ public function testUpdateGetUpdateList() { require_once $this->root . '/core/includes/update.inc'; - drupal_set_installed_schema_version('update_test_description', 8000); + \Drupal::service('update.update_hook_registry')->setInstalledVersion('update_test_description', 8000); \Drupal::moduleHandler()->loadInclude('update_test_description', 'install'); $updates = update_get_update_list(); diff --git a/core/tests/Drupal/KernelTests/Core/Extension/UpdateSchemaTest.php b/core/tests/Drupal/KernelTests/Core/Extension/UpdateSchemaTest.php index 607935a92d7c..bcdca37c0ba0 100644 --- a/core/tests/Drupal/KernelTests/Core/Extension/UpdateSchemaTest.php +++ b/core/tests/Drupal/KernelTests/Core/Extension/UpdateSchemaTest.php @@ -19,12 +19,12 @@ class UpdateSchemaTest extends KernelTestBase { /** * Tests the function parses schema updates as integer numbers. * - * @see drupal_get_schema_versions() + * @see \Drupal\Core\Update\UpdateHookRegistry::getAvailableUpdates() */ public function testDrupalGetSchemaVersionsInt() { \Drupal::state()->set('update_test_schema_version', 8001); $this->installSchema('update_test_schema', ['update_test_schema_table']); - $schema = drupal_get_schema_versions('update_test_schema'); + $schema = \Drupal::service('update.update_hook_registry')->getAvailableUpdates('update_test_schema'); foreach ($schema as $version) { $this->assertIsInt($version); } diff --git a/core/tests/Drupal/Tests/Core/Update/UpdateHookRegistryTest.php b/core/tests/Drupal/Tests/Core/Update/UpdateHookRegistryTest.php new file mode 100644 index 000000000000..a220a0ecb053 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Update/UpdateHookRegistryTest.php @@ -0,0 +1,147 @@ +<?php + +namespace Drupal\Tests\Core\Update; + +use Drupal\Core\KeyValueStore\KeyValueFactoryInterface; +use Drupal\Core\KeyValueStore\KeyValueStoreInterface; +use Drupal\Core\Update\UpdateHookRegistry; +use Drupal\Tests\UnitTestCase; + +/** + * Simulates a hook_update_N function. + */ +function under_test_update_3000() { + +} + +/** + * Simulates a hook_update_N function. + * + * When filtered this will be rejected. + */ +function bad_3() { + +} + +/** + * Simulates a hook_update_N function. + */ +function under_test_update_1() { + +} + +/** + * Simulates a hook_update_N functions. + * + * When filtered this will be rejected. + */ +function failed_22_update() { + +} + +/** + * Simulates a hook_update_N function. + */ +function under_test_update_20() { + +} + +/** + * Simulates a hook_update_N function. + * + * When filtered this will be rejected. + */ +function under_test_update_1234_failed() { + +} + +/** + * @coversDefaultClass \Drupal\Core\Update\UpdateHookRegistry + * @group Update + */ +class UpdateHookRegistryTest extends UnitTestCase { + + /** + * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface|\PHPUnit\Framework\MockObject\MockObject + */ + protected $keyValueStore; + + /** + * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface|\PHPUnit\Framework\MockObject\MockObject + */ + protected $keyValueFactory; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->keyValueFactory = $this->createMock(KeyValueFactoryInterface::class); + $this->keyValueStore = $this->createMock(KeyValueStoreInterface::class); + + $this->keyValueFactory + ->method('get') + ->with('system.schema') + ->willReturn($this->keyValueStore); + } + + /** + * @covers ::getAvailableUpdates + */ + public function testGetVersions() { + $module_name = 'drupal\tests\core\update\under_test'; + + $update_registry = new UpdateHookRegistry([], $this->keyValueStore); + + // Only under_test_update_X - passes through the filter. + $expected = [1, 20, 3000]; + $actual = $update_registry->getAvailableUpdates($module_name); + + $this->assertSame($expected, $actual); + } + + /** + * @covers ::getInstalledVersion + * @covers ::getAllInstalledVersions + * @covers ::setInstalledVersion + * @covers ::deleteInstalledVersion + */ + public function testGetInstalledVersion() { + $versions = [ + 'module1' => 1, + 'module2' => 20, + 'module3' => 3000, + ]; + + $this->keyValueStore + ->method('getAll') + ->willReturnCallback(static function () use (&$versions) { + return $versions; + }); + $this->keyValueStore + ->method('get') + ->willReturnCallback(static function ($key) use (&$versions) { + return $versions[$key]; + }); + $this->keyValueStore + ->method('delete') + ->willReturnCallback(static function ($key) use (&$versions) { + $versions[$key] = UpdateHookRegistry::SCHEMA_UNINSTALLED; + }); + $this->keyValueStore + ->method('set') + ->willReturnCallback(static function ($key, $value) use (&$versions) { + $versions[$key] = $value; + }); + + $update_registry = new UpdateHookRegistry([], $this->keyValueStore); + + $this->assertSame(3000, $update_registry->getInstalledVersion('module3')); + $update_registry->setInstalledVersion('module3', 3001); + $this->assertSame(3001, $update_registry->getInstalledVersion('module3')); + $this->assertSame($versions, $update_registry->getAllInstalledVersions()); + $update_registry->deleteInstalledVersion('module3'); + $this->assertSame(UpdateHookRegistry::SCHEMA_UNINSTALLED, $update_registry->getInstalledVersion('module3')); + } + +} diff --git a/core/tests/Drupal/Tests/UpdatePathTestTrait.php b/core/tests/Drupal/Tests/UpdatePathTestTrait.php index d3824943f622..3978f7e37c41 100644 --- a/core/tests/Drupal/Tests/UpdatePathTestTrait.php +++ b/core/tests/Drupal/Tests/UpdatePathTestTrait.php @@ -58,9 +58,7 @@ protected function runUpdates($update_url = NULL) { $this->fail('The update failed with the following message: "' . reset($failure)->getText() . '"'); } - // Ensure that there are no pending updates. Clear the schema version - // static cache first in case it was accessed before running updates. - drupal_get_installed_schema_version(NULL, TRUE); + // Ensure that there are no pending updates. foreach (['update', 'post_update'] as $update_type) { switch ($update_type) { case 'update': @@ -162,11 +160,7 @@ protected function doSelectionTest() { protected function ensureUpdatesToRun() { \Drupal::service('module_installer')->install(['update_script_test']); // Reset the schema so there is an update to run. - \Drupal::database()->update('key_value') - ->fields(['value' => serialize(8000)]) - ->condition('collection', 'system.schema') - ->condition('name', 'update_script_test') - ->execute(); + \Drupal::service('update.update_hook_registry')->setInstalledVersion('update_script_test', 8000); } } -- GitLab