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