annotate core/lib/Drupal/Core/Update/UpdateRegistry.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Update;
Chris@0 4
Chris@0 5 use Drupal\Core\Extension\Extension;
Chris@0 6 use Drupal\Core\Extension\ExtensionDiscovery;
Chris@0 7 use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
Chris@0 8
Chris@0 9 /**
Chris@0 10 * Provides all and missing update implementations.
Chris@0 11 *
Chris@0 12 * Note: This registry is specific to a type of updates, like 'post_update' as
Chris@0 13 * example.
Chris@0 14 *
Chris@0 15 * It therefore scans for functions named like the type of updates, so it looks
Chris@0 16 * like MODULE_UPDATETYPE_NAME() with NAME being a machine name.
Chris@0 17 */
Chris@0 18 class UpdateRegistry {
Chris@0 19
Chris@0 20 /**
Chris@0 21 * The used update name.
Chris@0 22 *
Chris@0 23 * @var string
Chris@0 24 */
Chris@0 25 protected $updateType = 'post_update';
Chris@0 26
Chris@0 27 /**
Chris@0 28 * The app root.
Chris@0 29 *
Chris@0 30 * @var string
Chris@0 31 */
Chris@0 32 protected $root;
Chris@0 33
Chris@0 34 /**
Chris@0 35 * The filename of the log file.
Chris@0 36 *
Chris@0 37 * @var string
Chris@0 38 */
Chris@0 39 protected $logFilename;
Chris@0 40
Chris@0 41 /**
Chris@0 42 * @var string[]
Chris@0 43 */
Chris@0 44 protected $enabledModules;
Chris@0 45
Chris@0 46 /**
Chris@0 47 * The key value storage.
Chris@0 48 *
Chris@0 49 * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
Chris@0 50 */
Chris@0 51 protected $keyValue;
Chris@0 52
Chris@0 53 /**
Chris@0 54 * Should we respect update functions in tests.
Chris@0 55 *
Chris@0 56 * @var bool|null
Chris@0 57 */
Chris@0 58 protected $includeTests = NULL;
Chris@0 59
Chris@0 60 /**
Chris@0 61 * The site path.
Chris@0 62 *
Chris@0 63 * @var string
Chris@0 64 */
Chris@0 65 protected $sitePath;
Chris@0 66
Chris@0 67 /**
Chris@0 68 * Constructs a new UpdateRegistry.
Chris@0 69 *
Chris@0 70 * @param string $root
Chris@0 71 * The app root.
Chris@0 72 * @param string $site_path
Chris@0 73 * The site path.
Chris@0 74 * @param string[] $enabled_modules
Chris@0 75 * A list of enabled modules.
Chris@0 76 * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $key_value
Chris@0 77 * The key value store.
Chris@0 78 * @param bool|null $include_tests
Chris@0 79 * (optional) A flag whether to include tests in the scanning of modules.
Chris@0 80 */
Chris@0 81 public function __construct($root, $site_path, array $enabled_modules, KeyValueStoreInterface $key_value, $include_tests = NULL) {
Chris@0 82 $this->root = $root;
Chris@0 83 $this->sitePath = $site_path;
Chris@0 84 $this->enabledModules = $enabled_modules;
Chris@0 85 $this->keyValue = $key_value;
Chris@0 86 $this->includeTests = $include_tests;
Chris@0 87 }
Chris@0 88
Chris@0 89 /**
Chris@0 90 * Gets all available update functions.
Chris@0 91 *
Chris@0 92 * @return callable[]
Chris@0 93 * A list of update functions.
Chris@0 94 */
Chris@0 95 protected function getAvailableUpdateFunctions() {
Chris@0 96 $regexp = '/^(?<module>.+)_' . $this->updateType . '_(?<name>.+)$/';
Chris@0 97 $functions = get_defined_functions();
Chris@0 98
Chris@0 99 $updates = [];
Chris@0 100 foreach (preg_grep('/_' . $this->updateType . '_/', $functions['user']) as $function) {
Chris@0 101 // If this function is a module update function, add it to the list of
Chris@0 102 // module updates.
Chris@0 103 if (preg_match($regexp, $function, $matches)) {
Chris@0 104 if (in_array($matches['module'], $this->enabledModules)) {
Chris@0 105 $updates[] = $matches['module'] . '_' . $this->updateType . '_' . $matches['name'];
Chris@0 106 }
Chris@0 107 }
Chris@0 108 }
Chris@0 109
Chris@0 110 // Ensure that the update order is deterministic.
Chris@0 111 sort($updates);
Chris@0 112 return $updates;
Chris@0 113 }
Chris@0 114
Chris@0 115 /**
Chris@0 116 * Find all update functions that haven't been executed.
Chris@0 117 *
Chris@0 118 * @return callable[]
Chris@0 119 * A list of update functions.
Chris@0 120 */
Chris@0 121 public function getPendingUpdateFunctions() {
Chris@0 122 // We need a) the list of active modules (we get that from the config
Chris@0 123 // bootstrap factory) and b) the path to the modules, we use the extension
Chris@0 124 // discovery for that.
Chris@0 125
Chris@0 126 $this->scanExtensionsAndLoadUpdateFiles();
Chris@0 127
Chris@0 128 // First figure out which hook_{$this->updateType}_NAME got executed
Chris@0 129 // already.
Chris@0 130 $existing_update_functions = $this->keyValue->get('existing_updates', []);
Chris@0 131
Chris@0 132 $available_update_functions = $this->getAvailableUpdateFunctions();
Chris@0 133 $not_executed_update_functions = array_diff($available_update_functions, $existing_update_functions);
Chris@0 134
Chris@0 135 return $not_executed_update_functions;
Chris@0 136 }
Chris@0 137
Chris@0 138 /**
Chris@0 139 * Loads all update files for a given list of extension.
Chris@0 140 *
Chris@0 141 * @param \Drupal\Core\Extension\Extension[] $module_extensions
Chris@0 142 * The extensions used for loading.
Chris@0 143 */
Chris@0 144 protected function loadUpdateFiles(array $module_extensions) {
Chris@0 145 // Load all the {$this->updateType}.php files.
Chris@0 146 foreach ($this->enabledModules as $module) {
Chris@0 147 if (isset($module_extensions[$module])) {
Chris@0 148 $this->loadUpdateFile($module_extensions[$module]);
Chris@0 149 }
Chris@0 150 }
Chris@0 151 }
Chris@0 152
Chris@0 153 /**
Chris@0 154 * Loads the {$this->updateType}.php file for a given extension.
Chris@0 155 *
Chris@0 156 * @param \Drupal\Core\Extension\Extension $module
Chris@0 157 * The extension of the module to load its file.
Chris@0 158 */
Chris@0 159 protected function loadUpdateFile(Extension $module) {
Chris@0 160 $filename = $this->root . '/' . $module->getPath() . '/' . $module->getName() . ".{$this->updateType}.php";
Chris@0 161 if (file_exists($filename)) {
Chris@0 162 include_once $filename;
Chris@0 163 }
Chris@0 164 }
Chris@0 165
Chris@0 166 /**
Chris@0 167 * Returns a list of all the pending updates.
Chris@0 168 *
Chris@0 169 * @return array[]
Chris@0 170 * An associative array keyed by module name which contains all information
Chris@0 171 * about database updates that need to be run, and any updates that are not
Chris@0 172 * going to proceed due to missing requirements.
Chris@0 173 *
Chris@0 174 * The subarray for each module can contain the following keys:
Chris@0 175 * - start: The starting update that is to be processed. If this does not
Chris@0 176 * exist then do not process any updates for this module as there are
Chris@0 177 * other requirements that need to be resolved.
Chris@0 178 * - pending: An array of all the pending updates for the module including
Chris@0 179 * the description from source code comment for each update function.
Chris@0 180 * This array is keyed by the update name.
Chris@0 181 */
Chris@0 182 public function getPendingUpdateInformation() {
Chris@0 183 $functions = $this->getPendingUpdateFunctions();
Chris@0 184
Chris@0 185 $ret = [];
Chris@0 186 foreach ($functions as $function) {
Chris@0 187 list($module, $update) = explode("_{$this->updateType}_", $function);
Chris@0 188 // The description for an update comes from its Doxygen.
Chris@0 189 $func = new \ReflectionFunction($function);
Chris@0 190 $description = trim(str_replace(["\n", '*', '/'], '', $func->getDocComment()), ' ');
Chris@0 191 $ret[$module]['pending'][$update] = $description;
Chris@0 192 if (!isset($ret[$module]['start'])) {
Chris@0 193 $ret[$module]['start'] = $update;
Chris@0 194 }
Chris@0 195 }
Chris@0 196 return $ret;
Chris@0 197 }
Chris@0 198
Chris@0 199 /**
Chris@17 200 * Registers that update functions were executed.
Chris@0 201 *
Chris@0 202 * @param string[] $function_names
Chris@0 203 * The executed update functions.
Chris@0 204 *
Chris@0 205 * @return $this
Chris@0 206 */
Chris@0 207 public function registerInvokedUpdates(array $function_names) {
Chris@0 208 $executed_updates = $this->keyValue->get('existing_updates', []);
Chris@0 209 $executed_updates = array_merge($executed_updates, $function_names);
Chris@0 210 $this->keyValue->set('existing_updates', $executed_updates);
Chris@0 211
Chris@0 212 return $this;
Chris@0 213 }
Chris@0 214
Chris@0 215 /**
Chris@0 216 * Returns all available updates for a given module.
Chris@0 217 *
Chris@0 218 * @param string $module_name
Chris@0 219 * The module name.
Chris@0 220 *
Chris@0 221 * @return callable[]
Chris@0 222 * A list of update functions.
Chris@0 223 */
Chris@0 224 public function getModuleUpdateFunctions($module_name) {
Chris@0 225 $this->scanExtensionsAndLoadUpdateFiles();
Chris@0 226 $all_functions = $this->getAvailableUpdateFunctions();
Chris@0 227
Chris@0 228 return array_filter($all_functions, function ($function_name) use ($module_name) {
Chris@0 229 list($function_module_name,) = explode("_{$this->updateType}_", $function_name);
Chris@0 230 return $function_module_name === $module_name;
Chris@0 231 });
Chris@0 232 }
Chris@0 233
Chris@0 234 /**
Chris@0 235 * Scans all module + profile extensions and load the update files.
Chris@0 236 */
Chris@0 237 protected function scanExtensionsAndLoadUpdateFiles() {
Chris@0 238 // Scan the module list.
Chris@0 239 $extension_discovery = new ExtensionDiscovery($this->root, FALSE, [], $this->sitePath);
Chris@0 240 $module_extensions = $extension_discovery->scan('module');
Chris@0 241
Chris@0 242 $profile_extensions = $extension_discovery->scan('profile');
Chris@0 243 $extensions = array_merge($module_extensions, $profile_extensions);
Chris@0 244
Chris@0 245 $this->loadUpdateFiles($extensions);
Chris@0 246 }
Chris@0 247
Chris@0 248 /**
Chris@0 249 * Filters out already executed update functions by module.
Chris@0 250 *
Chris@0 251 * @param string $module
Chris@0 252 * The module name.
Chris@0 253 */
Chris@0 254 public function filterOutInvokedUpdatesByModule($module) {
Chris@0 255 $existing_update_functions = $this->keyValue->get('existing_updates', []);
Chris@0 256
Chris@0 257 $remaining_update_functions = array_filter($existing_update_functions, function ($function_name) use ($module) {
Chris@0 258 return strpos($function_name, "{$module}_{$this->updateType}_") !== 0;
Chris@0 259 });
Chris@0 260
Chris@0 261 $this->keyValue->set('existing_updates', array_values($remaining_update_functions));
Chris@0 262 }
Chris@0 263
Chris@0 264 }