Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 52 |
|
0.00% |
0 / 19 |
CRAP | |
0.00% |
0 / 1 |
Task | |
0.00% |
0 / 52 |
|
0.00% |
0 / 19 |
702 | |
0.00% |
0 / 1 |
execute | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getName | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getDescription | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getDescriptionMessage | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
isSkipped | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAliases | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDependencies | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getProvidedNames | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
initBase | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getContext | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getConfigVar | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getOption | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getConnection | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
definitelyGetConnection | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
applySourceFile | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
getSchemaBasePath | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSqlFilePath | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getServices | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getHookContainer | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getVirtualDomains | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
assertDependsOn | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Installer\Task; |
4 | |
5 | use MediaWiki\HookContainer\HookContainer; |
6 | use MediaWiki\Installer\ConnectionStatus; |
7 | use MediaWiki\Language\RawMessage; |
8 | use MediaWiki\MediaWikiServices; |
9 | use MediaWiki\Message\Message; |
10 | use MediaWiki\Status\Status; |
11 | use RuntimeException; |
12 | use Wikimedia\Message\MessageSpecifier; |
13 | use Wikimedia\Rdbms\DBQueryError; |
14 | use Wikimedia\Rdbms\IDatabase; |
15 | use Wikimedia\Rdbms\IMaintainableDatabase; |
16 | |
17 | /** |
18 | * Base class for installer tasks |
19 | * |
20 | * @stable to extend |
21 | * @since 1.44 |
22 | */ |
23 | abstract class Task { |
24 | /** @var ITaskContext|null */ |
25 | private $context; |
26 | /** @var string|null */ |
27 | private $schemaBasePath; |
28 | |
29 | /** |
30 | * Execute the task. |
31 | * |
32 | * Notes for implementors: |
33 | * - Unless the task is registered with a specific profile, tasks will run |
34 | * in both installPreConfigured.php and the traditional unconfigured |
35 | * environment. The global state differs between these environments. |
36 | * |
37 | * - Tasks almost always have dependencies. Override getDependencies(). |
38 | * |
39 | * - If you need MediaWikiServices, declare a dependency on 'services' and |
40 | * use getServices(). The dependency ensures that the task is run when |
41 | * the global service container is functional. |
42 | * |
43 | * @return Status |
44 | */ |
45 | abstract public function execute(): Status; |
46 | |
47 | /** |
48 | * Get the symbolic name of the task. |
49 | * |
50 | * @return string |
51 | */ |
52 | abstract public function getName(); |
53 | |
54 | /** |
55 | * Get a human-readable description of what this task does, for use as a |
56 | * progress message. This may either be English text or a MessageSpecifier. |
57 | * It is unsafe to use an extension message. |
58 | * |
59 | * @stable to override |
60 | * @return MessageSpecifier|string |
61 | */ |
62 | public function getDescription() { |
63 | // Messages: config-install-database, config-install-tables, config-install-interwiki, |
64 | // config-install-stats, config-install-keys, config-install-sysop, config-install-mainpage, |
65 | // config-install-extensions |
66 | $msg = wfMessage( "config-install-" . $this->getName() ); |
67 | if ( $msg->exists() ) { |
68 | return $msg; |
69 | } else { |
70 | return wfMessage( "config-install-generic", $this->getName() ); |
71 | } |
72 | } |
73 | |
74 | /** |
75 | * Get the description as a Message object |
76 | * |
77 | * @internal |
78 | * @return Message |
79 | */ |
80 | final public function getDescriptionMessage() { |
81 | $msg = $this->getDescription(); |
82 | if ( $msg instanceof Message ) { |
83 | return $msg; |
84 | } elseif ( $msg instanceof MessageSpecifier ) { |
85 | return new Message( $msg ); |
86 | } else { |
87 | return new RawMessage( $msg ); |
88 | } |
89 | } |
90 | |
91 | /** |
92 | * Override this to return true to skip the task. If this returns true, |
93 | * execute() will not be called, and start/end messages will not be |
94 | * produced. |
95 | * |
96 | * @stable to override |
97 | * @return bool |
98 | */ |
99 | public function isSkipped(): bool { |
100 | return false; |
101 | } |
102 | |
103 | /** |
104 | * Get alternative names of this task. These aliases can be used to fulfill |
105 | * dependencies of other tasks. |
106 | * |
107 | * @stable to override |
108 | * @return string|string[] |
109 | */ |
110 | public function getAliases() { |
111 | return []; |
112 | } |
113 | |
114 | /** |
115 | * Get a list of names or aliases of tasks that must be done prior to this task. |
116 | * |
117 | * @stable to override |
118 | * @return string|string[] |
119 | */ |
120 | public function getDependencies() { |
121 | return []; |
122 | } |
123 | |
124 | /** |
125 | * Get a list of names of objects that this task promises to provide |
126 | * via $this->getContext()->provide(). |
127 | * |
128 | * If this is non-empty, the task is a scheduled provider, which means that |
129 | * it is not persistently complete after it has been run. If installation |
130 | * is interrupted, it might need to be run again. |
131 | * |
132 | * @stable to override |
133 | * @return string|string[] |
134 | */ |
135 | public function getProvidedNames() { |
136 | return []; |
137 | } |
138 | |
139 | /** |
140 | * Inject the base class dependencies and configuration |
141 | * |
142 | * @param ITaskContext $context |
143 | * @param string $schemaBasePath |
144 | */ |
145 | final public function initBase( |
146 | ITaskContext $context, |
147 | string $schemaBasePath |
148 | ) { |
149 | $this->context = $context; |
150 | $this->schemaBasePath = $schemaBasePath; |
151 | } |
152 | |
153 | /** |
154 | * Get the execution context. This will throw if initBase() has not been called. |
155 | * |
156 | * @return ITaskContext |
157 | */ |
158 | protected function getContext(): ITaskContext { |
159 | return $this->context; |
160 | } |
161 | |
162 | /** |
163 | * Get a configuration variable for the wiki being created. |
164 | * The name should not have a "wg" prefix. |
165 | * |
166 | * @param string $name |
167 | * @return mixed |
168 | */ |
169 | protected function getConfigVar( string $name ) { |
170 | return $this->getContext()->getConfigVar( $name ); |
171 | } |
172 | |
173 | /** |
174 | * Get an installer option value. |
175 | * |
176 | * @param string $name |
177 | * @return mixed |
178 | */ |
179 | protected function getOption( string $name ) { |
180 | return $this->getContext()->getOption( $name ); |
181 | } |
182 | |
183 | /** |
184 | * Connect to the database for a specified purpose |
185 | * |
186 | * @param string $type One of the ITaskContext::CONN_* constants. |
187 | * @return ConnectionStatus |
188 | */ |
189 | protected function getConnection( string $type ): ConnectionStatus { |
190 | return $this->getContext()->getConnection( $type ); |
191 | } |
192 | |
193 | /** |
194 | * Get a database connection, and throw if a connection could not be |
195 | * obtained. This is for the convenience of callers which expect a |
196 | * connection to already be cached. |
197 | * |
198 | * @param string $type |
199 | * @return IMaintainableDatabase |
200 | */ |
201 | protected function definitelyGetConnection( string $type ): IMaintainableDatabase { |
202 | $status = $this->getConnection( $type ); |
203 | if ( !$status->isOK() ) { |
204 | throw new RuntimeException( __METHOD__ . ': unexpected DB connection error' ); |
205 | } |
206 | return $status->getDB(); |
207 | } |
208 | |
209 | /** |
210 | * Apply a SQL source file to the database as part of running an installation step. |
211 | * |
212 | * @param IMaintainableDatabase $conn |
213 | * @param string $relPath |
214 | * @return Status |
215 | */ |
216 | protected function applySourceFile( IMaintainableDatabase $conn, string $relPath ) { |
217 | $path = $this->getSqlFilePath( $relPath ); |
218 | $status = Status::newGood(); |
219 | try { |
220 | $conn->doAtomicSection( __METHOD__, |
221 | static function () use ( $conn, $path ) { |
222 | $conn->sourceFile( $path ); |
223 | }, |
224 | IDatabase::ATOMIC_CANCELABLE |
225 | ); |
226 | } catch ( DBQueryError $e ) { |
227 | $status->fatal( "config-install-tables-failed", $e->getMessage() ); |
228 | } |
229 | return $status; |
230 | } |
231 | |
232 | /** |
233 | * Get the absolute base path for SQL schema files. |
234 | * |
235 | * For core tasks, this is $IP/maintenance. For extension tasks, this will |
236 | * be sql/ under the extension directory. |
237 | * |
238 | * It would be possible to make the extension path be configurable, but it |
239 | * is currently not needed since extensions typically do not create their |
240 | * tables by this mechanism. |
241 | * |
242 | * @return string |
243 | */ |
244 | protected function getSchemaBasePath(): string { |
245 | return $this->schemaBasePath; |
246 | } |
247 | |
248 | /** |
249 | * Return a path to the DBMS-specific SQL file if it exists, |
250 | * otherwise default SQL file. The path should be relative to the core or |
251 | * extension schema base path. |
252 | * |
253 | * @param string $filename |
254 | * @return string |
255 | */ |
256 | protected function getSqlFilePath( string $filename ) { |
257 | $type = $this->getContext()->getDbType(); |
258 | $base = $this->getSchemaBasePath(); |
259 | $dbmsSpecificFilePath = "$base/$type/$filename"; |
260 | if ( file_exists( $dbmsSpecificFilePath ) ) { |
261 | return $dbmsSpecificFilePath; |
262 | } else { |
263 | return "$base/$filename"; |
264 | } |
265 | } |
266 | |
267 | /** |
268 | * Get the restored services. Subclasses that want to call this must declare |
269 | * a dependency on "services". |
270 | * |
271 | * @return MediaWikiServices |
272 | */ |
273 | public function getServices(): MediaWikiServices { |
274 | $this->assertDependsOn( 'services' ); |
275 | return $this->getContext()->getProvision( 'services' ); |
276 | } |
277 | |
278 | /** |
279 | * Get a HookContainer suitable for calling LoadExtensionSchemaUpdates. |
280 | * Subclasses that want to call this must declare a dependency on |
281 | * "HookContainer". |
282 | * |
283 | * @return HookContainer |
284 | */ |
285 | public function getHookContainer(): HookContainer { |
286 | $this->assertDependsOn( 'HookContainer' ); |
287 | return $this->getContext()->getProvision( 'HookContainer' ); |
288 | } |
289 | |
290 | /* |
291 | * Get the array of database virtual domains declared in extensions. |
292 | * Subclasses that want to call this must declare a dependency on |
293 | * "VirtualDomains". |
294 | * |
295 | * @return array |
296 | */ |
297 | public function getVirtualDomains(): array { |
298 | $this->assertDependsOn( 'VirtualDomains' ); |
299 | return $this->getContext()->getProvision( 'VirtualDomains' ); |
300 | } |
301 | |
302 | private function assertDependsOn( $dependency ) { |
303 | $deps = (array)$this->getDependencies(); |
304 | if ( !in_array( $dependency, $deps, true ) ) { |
305 | throw new \RuntimeException( 'Task class "' . static::class . '" ' . |
306 | "does not declare a dependency on \"$dependency\"" ); |
307 | } |
308 | } |
309 | } |