These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Updater for link tracking tables after a page edit. |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | */ |
||
22 | |||
23 | use MediaWiki\MediaWikiServices; |
||
24 | use Wikimedia\ScopedCallback; |
||
0 ignored issues
–
show
|
|||
25 | |||
26 | /** |
||
27 | * Class the manages updates of *_link tables as well as similar extension-managed tables |
||
28 | * |
||
29 | * @note: LinksUpdate is managed by DeferredUpdates::execute(). Do not run this in a transaction. |
||
30 | * |
||
31 | * See docs/deferred.txt |
||
32 | */ |
||
33 | class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate { |
||
34 | // @todo make members protected, but make sure extensions don't break |
||
35 | |||
36 | /** @var int Page ID of the article linked from */ |
||
37 | public $mId; |
||
38 | |||
39 | /** @var Title Title object of the article linked from */ |
||
40 | public $mTitle; |
||
41 | |||
42 | /** @var ParserOutput */ |
||
43 | public $mParserOutput; |
||
44 | |||
45 | /** @var array Map of title strings to IDs for the links in the document */ |
||
46 | public $mLinks; |
||
47 | |||
48 | /** @var array DB keys of the images used, in the array key only */ |
||
49 | public $mImages; |
||
50 | |||
51 | /** @var array Map of title strings to IDs for the template references, including broken ones */ |
||
52 | public $mTemplates; |
||
53 | |||
54 | /** @var array URLs of external links, array key only */ |
||
55 | public $mExternals; |
||
56 | |||
57 | /** @var array Map of category names to sort keys */ |
||
58 | public $mCategories; |
||
59 | |||
60 | /** @var array Map of language codes to titles */ |
||
61 | public $mInterlangs; |
||
62 | |||
63 | /** @var array 2-D map of (prefix => DBK => 1) */ |
||
64 | public $mInterwikis; |
||
65 | |||
66 | /** @var array Map of arbitrary name to value */ |
||
67 | public $mProperties; |
||
68 | |||
69 | /** @var bool Whether to queue jobs for recursive updates */ |
||
70 | public $mRecursive; |
||
71 | |||
72 | /** @var Revision Revision for which this update has been triggered */ |
||
73 | private $mRevision; |
||
74 | |||
75 | /** |
||
76 | * @var null|array Added links if calculated. |
||
77 | */ |
||
78 | private $linkInsertions = null; |
||
79 | |||
80 | /** |
||
81 | * @var null|array Deleted links if calculated. |
||
82 | */ |
||
83 | private $linkDeletions = null; |
||
84 | |||
85 | /** |
||
86 | * @var null|array Added properties if calculated. |
||
87 | */ |
||
88 | private $propertyInsertions = null; |
||
89 | |||
90 | /** |
||
91 | * @var null|array Deleted properties if calculated. |
||
92 | */ |
||
93 | private $propertyDeletions = null; |
||
94 | |||
95 | /** |
||
96 | * @var User|null |
||
97 | */ |
||
98 | private $user; |
||
99 | |||
100 | /** @var IDatabase */ |
||
101 | private $db; |
||
102 | |||
103 | /** |
||
104 | * Constructor |
||
105 | * |
||
106 | * @param Title $title Title of the page we're updating |
||
107 | * @param ParserOutput $parserOutput Output from a full parse of this page |
||
108 | * @param bool $recursive Queue jobs for recursive updates? |
||
109 | * @throws MWException |
||
110 | */ |
||
111 | function __construct( Title $title, ParserOutput $parserOutput, $recursive = true ) { |
||
112 | parent::__construct(); |
||
113 | |||
114 | $this->mTitle = $title; |
||
115 | $this->mId = $title->getArticleID( Title::GAID_FOR_UPDATE ); |
||
116 | |||
117 | if ( !$this->mId ) { |
||
118 | throw new InvalidArgumentException( |
||
119 | "The Title object yields no ID. Perhaps the page doesn't exist?" |
||
120 | ); |
||
121 | } |
||
122 | |||
123 | $this->mParserOutput = $parserOutput; |
||
124 | |||
125 | $this->mLinks = $parserOutput->getLinks(); |
||
126 | $this->mImages = $parserOutput->getImages(); |
||
127 | $this->mTemplates = $parserOutput->getTemplates(); |
||
128 | $this->mExternals = $parserOutput->getExternalLinks(); |
||
129 | $this->mCategories = $parserOutput->getCategories(); |
||
130 | $this->mProperties = $parserOutput->getProperties(); |
||
131 | $this->mInterwikis = $parserOutput->getInterwikiLinks(); |
||
132 | |||
133 | # Convert the format of the interlanguage links |
||
134 | # I didn't want to change it in the ParserOutput, because that array is passed all |
||
135 | # the way back to the skin, so either a skin API break would be required, or an |
||
136 | # inefficient back-conversion. |
||
137 | $ill = $parserOutput->getLanguageLinks(); |
||
138 | $this->mInterlangs = []; |
||
139 | foreach ( $ill as $link ) { |
||
140 | list( $key, $title ) = explode( ':', $link, 2 ); |
||
141 | $this->mInterlangs[$key] = $title; |
||
142 | } |
||
143 | |||
144 | foreach ( $this->mCategories as &$sortkey ) { |
||
145 | # If the sortkey is longer then 255 bytes, |
||
146 | # it truncated by DB, and then doesn't get |
||
147 | # matched when comparing existing vs current |
||
148 | # categories, causing bug 25254. |
||
149 | # Also. substr behaves weird when given "". |
||
150 | if ( $sortkey !== '' ) { |
||
151 | $sortkey = substr( $sortkey, 0, 255 ); |
||
152 | } |
||
153 | } |
||
154 | |||
155 | $this->mRecursive = $recursive; |
||
156 | |||
157 | Hooks::run( 'LinksUpdateConstructed', [ &$this ] ); |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * Update link tables with outgoing links from an updated article |
||
162 | * |
||
163 | * @note: this is managed by DeferredUpdates::execute(). Do not run this in a transaction. |
||
164 | */ |
||
165 | public function doUpdate() { |
||
166 | if ( $this->ticket ) { |
||
167 | // Make sure all links update threads see the changes of each other. |
||
168 | // This handles the case when updates have to batched into several COMMITs. |
||
169 | $scopedLock = self::acquirePageLock( $this->getDB(), $this->mId ); |
||
170 | } |
||
171 | |||
172 | Hooks::run( 'LinksUpdate', [ &$this ] ); |
||
173 | $this->doIncrementalUpdate(); |
||
174 | |||
175 | // Commit and release the lock (if set) |
||
176 | ScopedCallback::consume( $scopedLock ); |
||
177 | // Run post-commit hooks without DBO_TRX |
||
178 | $this->getDB()->onTransactionIdle( |
||
179 | function () { |
||
180 | Hooks::run( 'LinksUpdateComplete', [ &$this, $this->ticket ] ); |
||
181 | }, |
||
182 | __METHOD__ |
||
183 | ); |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * Acquire a lock for performing link table updates for a page on a DB |
||
188 | * |
||
189 | * @param IDatabase $dbw |
||
190 | * @param integer $pageId |
||
191 | * @param string $why One of (job, atomicity) |
||
192 | * @return ScopedCallback |
||
193 | * @throws RuntimeException |
||
194 | * @since 1.27 |
||
195 | */ |
||
196 | public static function acquirePageLock( IDatabase $dbw, $pageId, $why = 'atomicity' ) { |
||
197 | $key = "LinksUpdate:$why:pageid:$pageId"; |
||
198 | $scopedLock = $dbw->getScopedLockAndFlush( $key, __METHOD__, 15 ); |
||
199 | if ( !$scopedLock ) { |
||
200 | throw new RuntimeException( "Could not acquire lock '$key'." ); |
||
201 | } |
||
202 | |||
203 | return $scopedLock; |
||
204 | } |
||
205 | |||
206 | protected function doIncrementalUpdate() { |
||
207 | # Page links |
||
208 | $existingPL = $this->getExistingLinks(); |
||
209 | $this->linkDeletions = $this->getLinkDeletions( $existingPL ); |
||
210 | $this->linkInsertions = $this->getLinkInsertions( $existingPL ); |
||
211 | $this->incrTableUpdate( 'pagelinks', 'pl', $this->linkDeletions, $this->linkInsertions ); |
||
212 | |||
213 | # Image links |
||
214 | $existingIL = $this->getExistingImages(); |
||
215 | $imageDeletes = $this->getImageDeletions( $existingIL ); |
||
216 | $this->incrTableUpdate( |
||
217 | 'imagelinks', |
||
218 | 'il', |
||
219 | $imageDeletes, |
||
220 | $this->getImageInsertions( $existingIL ) ); |
||
221 | |||
222 | # Invalidate all image description pages which had links added or removed |
||
223 | $imageUpdates = $imageDeletes + array_diff_key( $this->mImages, $existingIL ); |
||
224 | $this->invalidateImageDescriptions( $imageUpdates ); |
||
225 | |||
226 | # External links |
||
227 | $existingEL = $this->getExistingExternals(); |
||
228 | $this->incrTableUpdate( |
||
229 | 'externallinks', |
||
230 | 'el', |
||
231 | $this->getExternalDeletions( $existingEL ), |
||
232 | $this->getExternalInsertions( $existingEL ) ); |
||
233 | |||
234 | # Language links |
||
235 | $existingLL = $this->getExistingInterlangs(); |
||
236 | $this->incrTableUpdate( |
||
237 | 'langlinks', |
||
238 | 'll', |
||
239 | $this->getInterlangDeletions( $existingLL ), |
||
240 | $this->getInterlangInsertions( $existingLL ) ); |
||
241 | |||
242 | # Inline interwiki links |
||
243 | $existingIW = $this->getExistingInterwikis(); |
||
244 | $this->incrTableUpdate( |
||
245 | 'iwlinks', |
||
246 | 'iwl', |
||
247 | $this->getInterwikiDeletions( $existingIW ), |
||
248 | $this->getInterwikiInsertions( $existingIW ) ); |
||
249 | |||
250 | # Template links |
||
251 | $existingTL = $this->getExistingTemplates(); |
||
252 | $this->incrTableUpdate( |
||
253 | 'templatelinks', |
||
254 | 'tl', |
||
255 | $this->getTemplateDeletions( $existingTL ), |
||
256 | $this->getTemplateInsertions( $existingTL ) ); |
||
257 | |||
258 | # Category links |
||
259 | $existingCL = $this->getExistingCategories(); |
||
260 | $categoryDeletes = $this->getCategoryDeletions( $existingCL ); |
||
261 | $this->incrTableUpdate( |
||
262 | 'categorylinks', |
||
263 | 'cl', |
||
264 | $categoryDeletes, |
||
265 | $this->getCategoryInsertions( $existingCL ) ); |
||
266 | $categoryInserts = array_diff_assoc( $this->mCategories, $existingCL ); |
||
267 | $categoryUpdates = $categoryInserts + $categoryDeletes; |
||
268 | |||
269 | # Page properties |
||
270 | $existingPP = $this->getExistingProperties(); |
||
271 | $this->propertyDeletions = $this->getPropertyDeletions( $existingPP ); |
||
272 | $this->incrTableUpdate( |
||
273 | 'page_props', |
||
274 | 'pp', |
||
275 | $this->propertyDeletions, |
||
276 | $this->getPropertyInsertions( $existingPP ) ); |
||
277 | |||
278 | # Invalidate the necessary pages |
||
279 | $this->propertyInsertions = array_diff_assoc( $this->mProperties, $existingPP ); |
||
280 | $changed = $this->propertyDeletions + $this->propertyInsertions; |
||
281 | $this->invalidateProperties( $changed ); |
||
282 | |||
283 | # Invalidate all categories which were added, deleted or changed (set symmetric difference) |
||
284 | $this->invalidateCategories( $categoryUpdates ); |
||
285 | $this->updateCategoryCounts( $categoryInserts, $categoryDeletes ); |
||
286 | |||
287 | # Refresh links of all pages including this page |
||
288 | # This will be in a separate transaction |
||
289 | if ( $this->mRecursive ) { |
||
290 | $this->queueRecursiveJobs(); |
||
291 | } |
||
292 | |||
293 | # Update the links table freshness for this title |
||
294 | $this->updateLinksTimestamp(); |
||
295 | } |
||
296 | |||
297 | /** |
||
298 | * Queue recursive jobs for this page |
||
299 | * |
||
300 | * Which means do LinksUpdate on all pages that include the current page, |
||
301 | * using the job queue. |
||
302 | */ |
||
303 | protected function queueRecursiveJobs() { |
||
304 | self::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' ); |
||
305 | if ( $this->mTitle->getNamespace() == NS_FILE ) { |
||
306 | // Process imagelinks in case the title is or was a redirect |
||
307 | self::queueRecursiveJobsForTable( $this->mTitle, 'imagelinks' ); |
||
308 | } |
||
309 | |||
310 | $bc = $this->mTitle->getBacklinkCache(); |
||
311 | // Get jobs for cascade-protected backlinks for a high priority queue. |
||
312 | // If meta-templates change to using a new template, the new template |
||
313 | // should be implicitly protected as soon as possible, if applicable. |
||
314 | // These jobs duplicate a subset of the above ones, but can run sooner. |
||
315 | // Which ever runs first generally no-ops the other one. |
||
316 | $jobs = []; |
||
317 | foreach ( $bc->getCascadeProtectedLinks() as $title ) { |
||
318 | $jobs[] = RefreshLinksJob::newPrioritized( $title, [] ); |
||
319 | } |
||
320 | JobQueueGroup::singleton()->push( $jobs ); |
||
321 | } |
||
322 | |||
323 | /** |
||
324 | * Queue a RefreshLinks job for any table. |
||
325 | * |
||
326 | * @param Title $title Title to do job for |
||
327 | * @param string $table Table to use (e.g. 'templatelinks') |
||
328 | */ |
||
329 | public static function queueRecursiveJobsForTable( Title $title, $table ) { |
||
330 | if ( $title->getBacklinkCache()->hasLinks( $table ) ) { |
||
331 | $job = new RefreshLinksJob( |
||
332 | $title, |
||
333 | [ |
||
334 | 'table' => $table, |
||
335 | 'recursive' => true, |
||
336 | ] + Job::newRootJobParams( // "overall" refresh links job info |
||
337 | "refreshlinks:{$table}:{$title->getPrefixedText()}" |
||
338 | ) |
||
339 | ); |
||
340 | |||
341 | JobQueueGroup::singleton()->push( $job ); |
||
342 | } |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * @param array $cats |
||
347 | */ |
||
348 | private function invalidateCategories( $cats ) { |
||
349 | PurgeJobUtils::invalidatePages( $this->getDB(), NS_CATEGORY, array_keys( $cats ) ); |
||
350 | } |
||
351 | |||
352 | /** |
||
353 | * Update all the appropriate counts in the category table. |
||
354 | * @param array $added Associative array of category name => sort key |
||
355 | * @param array $deleted Associative array of category name => sort key |
||
356 | */ |
||
357 | private function updateCategoryCounts( array $added, array $deleted ) { |
||
358 | global $wgUpdateRowsPerQuery; |
||
359 | |||
360 | $wp = WikiPage::factory( $this->mTitle ); |
||
361 | $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory(); |
||
362 | |||
363 | View Code Duplication | foreach ( array_chunk( array_keys( $added ), $wgUpdateRowsPerQuery ) as $addBatch ) { |
|
364 | $wp->updateCategoryCounts( $addBatch, [], $this->mId ); |
||
365 | $factory->commitAndWaitForReplication( |
||
366 | __METHOD__, $this->ticket, [ 'wiki' => $this->getDB()->getWikiID() ] |
||
367 | ); |
||
368 | } |
||
369 | |||
370 | View Code Duplication | foreach ( array_chunk( array_keys( $deleted ), $wgUpdateRowsPerQuery ) as $deleteBatch ) { |
|
371 | $wp->updateCategoryCounts( [], $deleteBatch, $this->mId ); |
||
372 | $factory->commitAndWaitForReplication( |
||
373 | __METHOD__, $this->ticket, [ 'wiki' => $this->getDB()->getWikiID() ] |
||
374 | ); |
||
375 | } |
||
376 | } |
||
377 | |||
378 | /** |
||
379 | * @param array $images |
||
380 | */ |
||
381 | private function invalidateImageDescriptions( $images ) { |
||
382 | PurgeJobUtils::invalidatePages( $this->getDB(), NS_FILE, array_keys( $images ) ); |
||
383 | } |
||
384 | |||
385 | /** |
||
386 | * Update a table by doing a delete query then an insert query |
||
387 | * @param string $table Table name |
||
388 | * @param string $prefix Field name prefix |
||
389 | * @param array $deletions |
||
390 | * @param array $insertions Rows to insert |
||
391 | */ |
||
392 | private function incrTableUpdate( $table, $prefix, $deletions, $insertions ) { |
||
393 | $services = MediaWikiServices::getInstance(); |
||
394 | $bSize = $services->getMainConfig()->get( 'UpdateRowsPerQuery' ); |
||
395 | $factory = $services->getDBLoadBalancerFactory(); |
||
396 | |||
397 | if ( $table === 'page_props' ) { |
||
398 | $fromField = 'pp_page'; |
||
399 | } else { |
||
400 | $fromField = "{$prefix}_from"; |
||
401 | } |
||
402 | |||
403 | $deleteWheres = []; // list of WHERE clause arrays for each DB delete() call |
||
404 | if ( $table === 'pagelinks' || $table === 'templatelinks' || $table === 'iwlinks' ) { |
||
405 | $baseKey = ( $table === 'iwlinks' ) ? 'iwl_prefix' : "{$prefix}_namespace"; |
||
406 | |||
407 | $curBatchSize = 0; |
||
408 | $curDeletionBatch = []; |
||
409 | $deletionBatches = []; |
||
410 | foreach ( $deletions as $ns => $dbKeys ) { |
||
411 | foreach ( $dbKeys as $dbKey => $unused ) { |
||
412 | $curDeletionBatch[$ns][$dbKey] = 1; |
||
413 | if ( ++$curBatchSize >= $bSize ) { |
||
414 | $deletionBatches[] = $curDeletionBatch; |
||
415 | $curDeletionBatch = []; |
||
416 | $curBatchSize = 0; |
||
417 | } |
||
418 | } |
||
419 | } |
||
420 | if ( $curDeletionBatch ) { |
||
421 | $deletionBatches[] = $curDeletionBatch; |
||
422 | } |
||
423 | |||
424 | foreach ( $deletionBatches as $deletionBatch ) { |
||
425 | $deleteWheres[] = [ |
||
426 | $fromField => $this->mId, |
||
427 | $this->getDB()->makeWhereFrom2d( $deletionBatch, $baseKey, "{$prefix}_title" ) |
||
428 | ]; |
||
429 | } |
||
430 | } else { |
||
431 | if ( $table === 'langlinks' ) { |
||
432 | $toField = 'll_lang'; |
||
433 | } elseif ( $table === 'page_props' ) { |
||
434 | $toField = 'pp_propname'; |
||
435 | } else { |
||
436 | $toField = $prefix . '_to'; |
||
437 | } |
||
438 | |||
439 | $deletionBatches = array_chunk( array_keys( $deletions ), $bSize ); |
||
440 | foreach ( $deletionBatches as $deletionBatch ) { |
||
441 | $deleteWheres[] = [ $fromField => $this->mId, $toField => $deletionBatch ]; |
||
442 | } |
||
443 | } |
||
444 | |||
445 | foreach ( $deleteWheres as $deleteWhere ) { |
||
446 | $this->getDB()->delete( $table, $deleteWhere, __METHOD__ ); |
||
447 | $factory->commitAndWaitForReplication( |
||
448 | __METHOD__, $this->ticket, [ 'wiki' => $this->getDB()->getWikiID() ] |
||
449 | ); |
||
450 | } |
||
451 | |||
452 | $insertBatches = array_chunk( $insertions, $bSize ); |
||
453 | foreach ( $insertBatches as $insertBatch ) { |
||
454 | $this->getDB()->insert( $table, $insertBatch, __METHOD__, 'IGNORE' ); |
||
455 | $factory->commitAndWaitForReplication( |
||
456 | __METHOD__, $this->ticket, [ 'wiki' => $this->getDB()->getWikiID() ] |
||
457 | ); |
||
458 | } |
||
459 | |||
460 | if ( count( $insertions ) ) { |
||
461 | Hooks::run( 'LinksUpdateAfterInsert', [ $this, $table, $insertions ] ); |
||
462 | } |
||
463 | } |
||
464 | |||
465 | /** |
||
466 | * Get an array of pagelinks insertions for passing to the DB |
||
467 | * Skips the titles specified by the 2-D array $existing |
||
468 | * @param array $existing |
||
469 | * @return array |
||
470 | */ |
||
471 | View Code Duplication | private function getLinkInsertions( $existing = [] ) { |
|
472 | $arr = []; |
||
473 | foreach ( $this->mLinks as $ns => $dbkeys ) { |
||
474 | $diffs = isset( $existing[$ns] ) |
||
475 | ? array_diff_key( $dbkeys, $existing[$ns] ) |
||
476 | : $dbkeys; |
||
477 | foreach ( $diffs as $dbk => $id ) { |
||
478 | $arr[] = [ |
||
479 | 'pl_from' => $this->mId, |
||
480 | 'pl_from_namespace' => $this->mTitle->getNamespace(), |
||
481 | 'pl_namespace' => $ns, |
||
482 | 'pl_title' => $dbk |
||
483 | ]; |
||
484 | } |
||
485 | } |
||
486 | |||
487 | return $arr; |
||
488 | } |
||
489 | |||
490 | /** |
||
491 | * Get an array of template insertions. Like getLinkInsertions() |
||
492 | * @param array $existing |
||
493 | * @return array |
||
494 | */ |
||
495 | View Code Duplication | private function getTemplateInsertions( $existing = [] ) { |
|
496 | $arr = []; |
||
497 | foreach ( $this->mTemplates as $ns => $dbkeys ) { |
||
498 | $diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys; |
||
499 | foreach ( $diffs as $dbk => $id ) { |
||
500 | $arr[] = [ |
||
501 | 'tl_from' => $this->mId, |
||
502 | 'tl_from_namespace' => $this->mTitle->getNamespace(), |
||
503 | 'tl_namespace' => $ns, |
||
504 | 'tl_title' => $dbk |
||
505 | ]; |
||
506 | } |
||
507 | } |
||
508 | |||
509 | return $arr; |
||
510 | } |
||
511 | |||
512 | /** |
||
513 | * Get an array of image insertions |
||
514 | * Skips the names specified in $existing |
||
515 | * @param array $existing |
||
516 | * @return array |
||
517 | */ |
||
518 | private function getImageInsertions( $existing = [] ) { |
||
519 | $arr = []; |
||
520 | $diffs = array_diff_key( $this->mImages, $existing ); |
||
521 | foreach ( $diffs as $iname => $dummy ) { |
||
522 | $arr[] = [ |
||
523 | 'il_from' => $this->mId, |
||
524 | 'il_from_namespace' => $this->mTitle->getNamespace(), |
||
525 | 'il_to' => $iname |
||
526 | ]; |
||
527 | } |
||
528 | |||
529 | return $arr; |
||
530 | } |
||
531 | |||
532 | /** |
||
533 | * Get an array of externallinks insertions. Skips the names specified in $existing |
||
534 | * @param array $existing |
||
535 | * @return array |
||
536 | */ |
||
537 | private function getExternalInsertions( $existing = [] ) { |
||
538 | $arr = []; |
||
539 | $diffs = array_diff_key( $this->mExternals, $existing ); |
||
540 | foreach ( $diffs as $url => $dummy ) { |
||
541 | foreach ( wfMakeUrlIndexes( $url ) as $index ) { |
||
542 | $arr[] = [ |
||
543 | 'el_id' => $this->getDB()->nextSequenceValue( 'externallinks_el_id_seq' ), |
||
544 | 'el_from' => $this->mId, |
||
545 | 'el_to' => $url, |
||
546 | 'el_index' => $index, |
||
547 | ]; |
||
548 | } |
||
549 | } |
||
550 | |||
551 | return $arr; |
||
552 | } |
||
553 | |||
554 | /** |
||
555 | * Get an array of category insertions |
||
556 | * |
||
557 | * @param array $existing Mapping existing category names to sort keys. If both |
||
558 | * match a link in $this, the link will be omitted from the output |
||
559 | * |
||
560 | * @return array |
||
561 | */ |
||
562 | private function getCategoryInsertions( $existing = [] ) { |
||
563 | global $wgContLang, $wgCategoryCollation; |
||
564 | $diffs = array_diff_assoc( $this->mCategories, $existing ); |
||
565 | $arr = []; |
||
566 | foreach ( $diffs as $name => $prefix ) { |
||
567 | $nt = Title::makeTitleSafe( NS_CATEGORY, $name ); |
||
568 | $wgContLang->findVariantLink( $name, $nt, true ); |
||
569 | |||
570 | View Code Duplication | if ( $this->mTitle->getNamespace() == NS_CATEGORY ) { |
|
571 | $type = 'subcat'; |
||
572 | } elseif ( $this->mTitle->getNamespace() == NS_FILE ) { |
||
573 | $type = 'file'; |
||
574 | } else { |
||
575 | $type = 'page'; |
||
576 | } |
||
577 | |||
578 | # Treat custom sortkeys as a prefix, so that if multiple |
||
579 | # things are forced to sort as '*' or something, they'll |
||
580 | # sort properly in the category rather than in page_id |
||
581 | # order or such. |
||
582 | $sortkey = Collation::singleton()->getSortKey( |
||
583 | $this->mTitle->getCategorySortkey( $prefix ) ); |
||
584 | |||
585 | $arr[] = [ |
||
586 | 'cl_from' => $this->mId, |
||
587 | 'cl_to' => $name, |
||
588 | 'cl_sortkey' => $sortkey, |
||
589 | 'cl_timestamp' => $this->getDB()->timestamp(), |
||
590 | 'cl_sortkey_prefix' => $prefix, |
||
591 | 'cl_collation' => $wgCategoryCollation, |
||
592 | 'cl_type' => $type, |
||
593 | ]; |
||
594 | } |
||
595 | |||
596 | return $arr; |
||
597 | } |
||
598 | |||
599 | /** |
||
600 | * Get an array of interlanguage link insertions |
||
601 | * |
||
602 | * @param array $existing Mapping existing language codes to titles |
||
603 | * |
||
604 | * @return array |
||
605 | */ |
||
606 | private function getInterlangInsertions( $existing = [] ) { |
||
607 | $diffs = array_diff_assoc( $this->mInterlangs, $existing ); |
||
608 | $arr = []; |
||
609 | foreach ( $diffs as $lang => $title ) { |
||
610 | $arr[] = [ |
||
611 | 'll_from' => $this->mId, |
||
612 | 'll_lang' => $lang, |
||
613 | 'll_title' => $title |
||
614 | ]; |
||
615 | } |
||
616 | |||
617 | return $arr; |
||
618 | } |
||
619 | |||
620 | /** |
||
621 | * Get an array of page property insertions |
||
622 | * @param array $existing |
||
623 | * @return array |
||
624 | */ |
||
625 | function getPropertyInsertions( $existing = [] ) { |
||
626 | $diffs = array_diff_assoc( $this->mProperties, $existing ); |
||
627 | |||
628 | $arr = []; |
||
629 | foreach ( array_keys( $diffs ) as $name ) { |
||
630 | $arr[] = $this->getPagePropRowData( $name ); |
||
631 | } |
||
632 | |||
633 | return $arr; |
||
634 | } |
||
635 | |||
636 | /** |
||
637 | * Returns an associative array to be used for inserting a row into |
||
638 | * the page_props table. Besides the given property name, this will |
||
639 | * include the page id from $this->mId and any property value from |
||
640 | * $this->mProperties. |
||
641 | * |
||
642 | * The array returned will include the pp_sortkey field if this |
||
643 | * is present in the database (as indicated by $wgPagePropsHaveSortkey). |
||
644 | * The sortkey value is currently determined by getPropertySortKeyValue(). |
||
645 | * |
||
646 | * @note this assumes that $this->mProperties[$prop] is defined. |
||
647 | * |
||
648 | * @param string $prop The name of the property. |
||
649 | * |
||
650 | * @return array |
||
651 | */ |
||
652 | private function getPagePropRowData( $prop ) { |
||
653 | global $wgPagePropsHaveSortkey; |
||
654 | |||
655 | $value = $this->mProperties[$prop]; |
||
656 | |||
657 | $row = [ |
||
658 | 'pp_page' => $this->mId, |
||
659 | 'pp_propname' => $prop, |
||
660 | 'pp_value' => $value, |
||
661 | ]; |
||
662 | |||
663 | if ( $wgPagePropsHaveSortkey ) { |
||
664 | $row['pp_sortkey'] = $this->getPropertySortKeyValue( $value ); |
||
665 | } |
||
666 | |||
667 | return $row; |
||
668 | } |
||
669 | |||
670 | /** |
||
671 | * Determines the sort key for the given property value. |
||
672 | * This will return $value if it is a float or int, |
||
673 | * 1 or resp. 0 if it is a bool, and null otherwise. |
||
674 | * |
||
675 | * @note In the future, we may allow the sortkey to be specified explicitly |
||
676 | * in ParserOutput::setProperty. |
||
677 | * |
||
678 | * @param mixed $value |
||
679 | * |
||
680 | * @return float|null |
||
681 | */ |
||
682 | private function getPropertySortKeyValue( $value ) { |
||
683 | if ( is_int( $value ) || is_float( $value ) || is_bool( $value ) ) { |
||
684 | return floatval( $value ); |
||
685 | } |
||
686 | |||
687 | return null; |
||
688 | } |
||
689 | |||
690 | /** |
||
691 | * Get an array of interwiki insertions for passing to the DB |
||
692 | * Skips the titles specified by the 2-D array $existing |
||
693 | * @param array $existing |
||
694 | * @return array |
||
695 | */ |
||
696 | private function getInterwikiInsertions( $existing = [] ) { |
||
697 | $arr = []; |
||
698 | foreach ( $this->mInterwikis as $prefix => $dbkeys ) { |
||
699 | $diffs = isset( $existing[$prefix] ) |
||
700 | ? array_diff_key( $dbkeys, $existing[$prefix] ) |
||
701 | : $dbkeys; |
||
702 | |||
703 | foreach ( $diffs as $dbk => $id ) { |
||
704 | $arr[] = [ |
||
705 | 'iwl_from' => $this->mId, |
||
706 | 'iwl_prefix' => $prefix, |
||
707 | 'iwl_title' => $dbk |
||
708 | ]; |
||
709 | } |
||
710 | } |
||
711 | |||
712 | return $arr; |
||
713 | } |
||
714 | |||
715 | /** |
||
716 | * Given an array of existing links, returns those links which are not in $this |
||
717 | * and thus should be deleted. |
||
718 | * @param array $existing |
||
719 | * @return array |
||
720 | */ |
||
721 | View Code Duplication | private function getLinkDeletions( $existing ) { |
|
722 | $del = []; |
||
723 | foreach ( $existing as $ns => $dbkeys ) { |
||
724 | if ( isset( $this->mLinks[$ns] ) ) { |
||
725 | $del[$ns] = array_diff_key( $existing[$ns], $this->mLinks[$ns] ); |
||
726 | } else { |
||
727 | $del[$ns] = $existing[$ns]; |
||
728 | } |
||
729 | } |
||
730 | |||
731 | return $del; |
||
732 | } |
||
733 | |||
734 | /** |
||
735 | * Given an array of existing templates, returns those templates which are not in $this |
||
736 | * and thus should be deleted. |
||
737 | * @param array $existing |
||
738 | * @return array |
||
739 | */ |
||
740 | View Code Duplication | private function getTemplateDeletions( $existing ) { |
|
741 | $del = []; |
||
742 | foreach ( $existing as $ns => $dbkeys ) { |
||
743 | if ( isset( $this->mTemplates[$ns] ) ) { |
||
744 | $del[$ns] = array_diff_key( $existing[$ns], $this->mTemplates[$ns] ); |
||
745 | } else { |
||
746 | $del[$ns] = $existing[$ns]; |
||
747 | } |
||
748 | } |
||
749 | |||
750 | return $del; |
||
751 | } |
||
752 | |||
753 | /** |
||
754 | * Given an array of existing images, returns those images which are not in $this |
||
755 | * and thus should be deleted. |
||
756 | * @param array $existing |
||
757 | * @return array |
||
758 | */ |
||
759 | private function getImageDeletions( $existing ) { |
||
760 | return array_diff_key( $existing, $this->mImages ); |
||
761 | } |
||
762 | |||
763 | /** |
||
764 | * Given an array of existing external links, returns those links which are not |
||
765 | * in $this and thus should be deleted. |
||
766 | * @param array $existing |
||
767 | * @return array |
||
768 | */ |
||
769 | private function getExternalDeletions( $existing ) { |
||
770 | return array_diff_key( $existing, $this->mExternals ); |
||
771 | } |
||
772 | |||
773 | /** |
||
774 | * Given an array of existing categories, returns those categories which are not in $this |
||
775 | * and thus should be deleted. |
||
776 | * @param array $existing |
||
777 | * @return array |
||
778 | */ |
||
779 | private function getCategoryDeletions( $existing ) { |
||
780 | return array_diff_assoc( $existing, $this->mCategories ); |
||
781 | } |
||
782 | |||
783 | /** |
||
784 | * Given an array of existing interlanguage links, returns those links which are not |
||
785 | * in $this and thus should be deleted. |
||
786 | * @param array $existing |
||
787 | * @return array |
||
788 | */ |
||
789 | private function getInterlangDeletions( $existing ) { |
||
790 | return array_diff_assoc( $existing, $this->mInterlangs ); |
||
791 | } |
||
792 | |||
793 | /** |
||
794 | * Get array of properties which should be deleted. |
||
795 | * @param array $existing |
||
796 | * @return array |
||
797 | */ |
||
798 | function getPropertyDeletions( $existing ) { |
||
799 | return array_diff_assoc( $existing, $this->mProperties ); |
||
800 | } |
||
801 | |||
802 | /** |
||
803 | * Given an array of existing interwiki links, returns those links which are not in $this |
||
804 | * and thus should be deleted. |
||
805 | * @param array $existing |
||
806 | * @return array |
||
807 | */ |
||
808 | View Code Duplication | private function getInterwikiDeletions( $existing ) { |
|
809 | $del = []; |
||
810 | foreach ( $existing as $prefix => $dbkeys ) { |
||
811 | if ( isset( $this->mInterwikis[$prefix] ) ) { |
||
812 | $del[$prefix] = array_diff_key( $existing[$prefix], $this->mInterwikis[$prefix] ); |
||
813 | } else { |
||
814 | $del[$prefix] = $existing[$prefix]; |
||
815 | } |
||
816 | } |
||
817 | |||
818 | return $del; |
||
819 | } |
||
820 | |||
821 | /** |
||
822 | * Get an array of existing links, as a 2-D array |
||
823 | * |
||
824 | * @return array |
||
825 | */ |
||
826 | private function getExistingLinks() { |
||
827 | $res = $this->getDB()->select( 'pagelinks', [ 'pl_namespace', 'pl_title' ], |
||
828 | [ 'pl_from' => $this->mId ], __METHOD__ ); |
||
829 | $arr = []; |
||
830 | foreach ( $res as $row ) { |
||
831 | if ( !isset( $arr[$row->pl_namespace] ) ) { |
||
832 | $arr[$row->pl_namespace] = []; |
||
833 | } |
||
834 | $arr[$row->pl_namespace][$row->pl_title] = 1; |
||
835 | } |
||
836 | |||
837 | return $arr; |
||
838 | } |
||
839 | |||
840 | /** |
||
841 | * Get an array of existing templates, as a 2-D array |
||
842 | * |
||
843 | * @return array |
||
844 | */ |
||
845 | private function getExistingTemplates() { |
||
846 | $res = $this->getDB()->select( 'templatelinks', [ 'tl_namespace', 'tl_title' ], |
||
847 | [ 'tl_from' => $this->mId ], __METHOD__ ); |
||
848 | $arr = []; |
||
849 | foreach ( $res as $row ) { |
||
850 | if ( !isset( $arr[$row->tl_namespace] ) ) { |
||
851 | $arr[$row->tl_namespace] = []; |
||
852 | } |
||
853 | $arr[$row->tl_namespace][$row->tl_title] = 1; |
||
854 | } |
||
855 | |||
856 | return $arr; |
||
857 | } |
||
858 | |||
859 | /** |
||
860 | * Get an array of existing images, image names in the keys |
||
861 | * |
||
862 | * @return array |
||
863 | */ |
||
864 | private function getExistingImages() { |
||
865 | $res = $this->getDB()->select( 'imagelinks', [ 'il_to' ], |
||
866 | [ 'il_from' => $this->mId ], __METHOD__ ); |
||
867 | $arr = []; |
||
868 | foreach ( $res as $row ) { |
||
869 | $arr[$row->il_to] = 1; |
||
870 | } |
||
871 | |||
872 | return $arr; |
||
873 | } |
||
874 | |||
875 | /** |
||
876 | * Get an array of existing external links, URLs in the keys |
||
877 | * |
||
878 | * @return array |
||
879 | */ |
||
880 | private function getExistingExternals() { |
||
881 | $res = $this->getDB()->select( 'externallinks', [ 'el_to' ], |
||
882 | [ 'el_from' => $this->mId ], __METHOD__ ); |
||
883 | $arr = []; |
||
884 | foreach ( $res as $row ) { |
||
885 | $arr[$row->el_to] = 1; |
||
886 | } |
||
887 | |||
888 | return $arr; |
||
889 | } |
||
890 | |||
891 | /** |
||
892 | * Get an array of existing categories, with the name in the key and sort key in the value. |
||
893 | * |
||
894 | * @return array |
||
895 | */ |
||
896 | private function getExistingCategories() { |
||
897 | $res = $this->getDB()->select( 'categorylinks', [ 'cl_to', 'cl_sortkey_prefix' ], |
||
898 | [ 'cl_from' => $this->mId ], __METHOD__ ); |
||
899 | $arr = []; |
||
900 | foreach ( $res as $row ) { |
||
901 | $arr[$row->cl_to] = $row->cl_sortkey_prefix; |
||
902 | } |
||
903 | |||
904 | return $arr; |
||
905 | } |
||
906 | |||
907 | /** |
||
908 | * Get an array of existing interlanguage links, with the language code in the key and the |
||
909 | * title in the value. |
||
910 | * |
||
911 | * @return array |
||
912 | */ |
||
913 | private function getExistingInterlangs() { |
||
914 | $res = $this->getDB()->select( 'langlinks', [ 'll_lang', 'll_title' ], |
||
915 | [ 'll_from' => $this->mId ], __METHOD__ ); |
||
916 | $arr = []; |
||
917 | foreach ( $res as $row ) { |
||
918 | $arr[$row->ll_lang] = $row->ll_title; |
||
919 | } |
||
920 | |||
921 | return $arr; |
||
922 | } |
||
923 | |||
924 | /** |
||
925 | * Get an array of existing inline interwiki links, as a 2-D array |
||
926 | * @return array (prefix => array(dbkey => 1)) |
||
927 | */ |
||
928 | private function getExistingInterwikis() { |
||
929 | $res = $this->getDB()->select( 'iwlinks', [ 'iwl_prefix', 'iwl_title' ], |
||
930 | [ 'iwl_from' => $this->mId ], __METHOD__ ); |
||
931 | $arr = []; |
||
932 | foreach ( $res as $row ) { |
||
933 | if ( !isset( $arr[$row->iwl_prefix] ) ) { |
||
934 | $arr[$row->iwl_prefix] = []; |
||
935 | } |
||
936 | $arr[$row->iwl_prefix][$row->iwl_title] = 1; |
||
937 | } |
||
938 | |||
939 | return $arr; |
||
940 | } |
||
941 | |||
942 | /** |
||
943 | * Get an array of existing categories, with the name in the key and sort key in the value. |
||
944 | * |
||
945 | * @return array Array of property names and values |
||
946 | */ |
||
947 | private function getExistingProperties() { |
||
948 | $res = $this->getDB()->select( 'page_props', [ 'pp_propname', 'pp_value' ], |
||
949 | [ 'pp_page' => $this->mId ], __METHOD__ ); |
||
950 | $arr = []; |
||
951 | foreach ( $res as $row ) { |
||
952 | $arr[$row->pp_propname] = $row->pp_value; |
||
953 | } |
||
954 | |||
955 | return $arr; |
||
956 | } |
||
957 | |||
958 | /** |
||
959 | * Return the title object of the page being updated |
||
960 | * @return Title |
||
961 | */ |
||
962 | public function getTitle() { |
||
963 | return $this->mTitle; |
||
964 | } |
||
965 | |||
966 | /** |
||
967 | * Returns parser output |
||
968 | * @since 1.19 |
||
969 | * @return ParserOutput |
||
970 | */ |
||
971 | public function getParserOutput() { |
||
972 | return $this->mParserOutput; |
||
973 | } |
||
974 | |||
975 | /** |
||
976 | * Return the list of images used as generated by the parser |
||
977 | * @return array |
||
978 | */ |
||
979 | public function getImages() { |
||
980 | return $this->mImages; |
||
981 | } |
||
982 | |||
983 | /** |
||
984 | * Set the revision corresponding to this LinksUpdate |
||
985 | * |
||
986 | * @since 1.27 |
||
987 | * |
||
988 | * @param Revision $revision |
||
989 | */ |
||
990 | public function setRevision( Revision $revision ) { |
||
991 | $this->mRevision = $revision; |
||
992 | } |
||
993 | |||
994 | /** |
||
995 | * @since 1.28 |
||
996 | * @return null|Revision |
||
997 | */ |
||
998 | public function getRevision() { |
||
999 | return $this->mRevision; |
||
1000 | } |
||
1001 | |||
1002 | /** |
||
1003 | * Set the User who triggered this LinksUpdate |
||
1004 | * |
||
1005 | * @since 1.27 |
||
1006 | * @param User $user |
||
1007 | */ |
||
1008 | public function setTriggeringUser( User $user ) { |
||
1009 | $this->user = $user; |
||
1010 | } |
||
1011 | |||
1012 | /** |
||
1013 | * @since 1.27 |
||
1014 | * @return null|User |
||
1015 | */ |
||
1016 | public function getTriggeringUser() { |
||
1017 | return $this->user; |
||
1018 | } |
||
1019 | |||
1020 | /** |
||
1021 | * Invalidate any necessary link lists related to page property changes |
||
1022 | * @param array $changed |
||
1023 | */ |
||
1024 | private function invalidateProperties( $changed ) { |
||
1025 | global $wgPagePropLinkInvalidations; |
||
1026 | |||
1027 | foreach ( $changed as $name => $value ) { |
||
1028 | if ( isset( $wgPagePropLinkInvalidations[$name] ) ) { |
||
1029 | $inv = $wgPagePropLinkInvalidations[$name]; |
||
1030 | if ( !is_array( $inv ) ) { |
||
1031 | $inv = [ $inv ]; |
||
1032 | } |
||
1033 | foreach ( $inv as $table ) { |
||
1034 | DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->mTitle, $table ) ); |
||
1035 | } |
||
1036 | } |
||
1037 | } |
||
1038 | } |
||
1039 | |||
1040 | /** |
||
1041 | * Fetch page links added by this LinksUpdate. Only available after the update is complete. |
||
1042 | * @since 1.22 |
||
1043 | * @return null|array Array of Titles |
||
1044 | */ |
||
1045 | public function getAddedLinks() { |
||
1046 | if ( $this->linkInsertions === null ) { |
||
1047 | return null; |
||
1048 | } |
||
1049 | $result = []; |
||
1050 | foreach ( $this->linkInsertions as $insertion ) { |
||
1051 | $result[] = Title::makeTitle( $insertion['pl_namespace'], $insertion['pl_title'] ); |
||
1052 | } |
||
1053 | |||
1054 | return $result; |
||
1055 | } |
||
1056 | |||
1057 | /** |
||
1058 | * Fetch page links removed by this LinksUpdate. Only available after the update is complete. |
||
1059 | * @since 1.22 |
||
1060 | * @return null|array Array of Titles |
||
1061 | */ |
||
1062 | public function getRemovedLinks() { |
||
1063 | if ( $this->linkDeletions === null ) { |
||
1064 | return null; |
||
1065 | } |
||
1066 | $result = []; |
||
1067 | foreach ( $this->linkDeletions as $ns => $titles ) { |
||
1068 | foreach ( $titles as $title => $unused ) { |
||
1069 | $result[] = Title::makeTitle( $ns, $title ); |
||
1070 | } |
||
1071 | } |
||
1072 | |||
1073 | return $result; |
||
1074 | } |
||
1075 | |||
1076 | /** |
||
1077 | * Fetch page properties added by this LinksUpdate. |
||
1078 | * Only available after the update is complete. |
||
1079 | * @since 1.28 |
||
1080 | * @return null|array |
||
1081 | */ |
||
1082 | public function getAddedProperties() { |
||
1083 | return $this->propertyInsertions; |
||
1084 | } |
||
1085 | |||
1086 | /** |
||
1087 | * Fetch page properties removed by this LinksUpdate. |
||
1088 | * Only available after the update is complete. |
||
1089 | * @since 1.28 |
||
1090 | * @return null|array |
||
1091 | */ |
||
1092 | public function getRemovedProperties() { |
||
1093 | return $this->propertyDeletions; |
||
1094 | } |
||
1095 | |||
1096 | /** |
||
1097 | * Update links table freshness |
||
1098 | */ |
||
1099 | private function updateLinksTimestamp() { |
||
1100 | if ( $this->mId ) { |
||
1101 | // The link updates made here only reflect the freshness of the parser output |
||
1102 | $timestamp = $this->mParserOutput->getCacheTime(); |
||
1103 | $this->getDB()->update( 'page', |
||
1104 | [ 'page_links_updated' => $this->getDB()->timestamp( $timestamp ) ], |
||
1105 | [ 'page_id' => $this->mId ], |
||
1106 | __METHOD__ |
||
1107 | ); |
||
1108 | } |
||
1109 | } |
||
1110 | |||
1111 | /** |
||
1112 | * @return IDatabase |
||
1113 | */ |
||
1114 | private function getDB() { |
||
1115 | if ( !$this->db ) { |
||
1116 | $this->db = wfGetDB( DB_MASTER ); |
||
1117 | } |
||
1118 | |||
1119 | return $this->db; |
||
1120 | } |
||
1121 | |||
1122 | public function getAsJobSpecification() { |
||
1123 | if ( $this->user ) { |
||
1124 | $userInfo = [ |
||
1125 | 'userId' => $this->user->getId(), |
||
1126 | 'userName' => $this->user->getName(), |
||
1127 | ]; |
||
1128 | } else { |
||
1129 | $userInfo = false; |
||
1130 | } |
||
1131 | |||
1132 | if ( $this->mRevision ) { |
||
1133 | $triggeringRevisionId = $this->mRevision->getId(); |
||
1134 | } else { |
||
1135 | $triggeringRevisionId = false; |
||
1136 | } |
||
1137 | |||
1138 | return [ |
||
1139 | 'wiki' => $this->getDB()->getWikiID(), |
||
1140 | 'job' => new JobSpecification( |
||
1141 | 'refreshLinksPrioritized', |
||
1142 | [ |
||
1143 | // Reuse the parser cache if it was saved |
||
1144 | 'rootJobTimestamp' => $this->mParserOutput->getCacheTime(), |
||
1145 | 'useRecursiveLinksUpdate' => $this->mRecursive, |
||
1146 | 'triggeringUser' => $userInfo, |
||
1147 | 'triggeringRevisionId' => $triggeringRevisionId, |
||
1148 | ], |
||
1149 | [ 'removeDuplicates' => true ], |
||
1150 | $this->getTitle() |
||
1151 | ) |
||
1152 | ]; |
||
1153 | } |
||
1154 | } |
||
1155 |
Let’s assume that you have a directory layout like this:
and let’s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as
OtherDir/Foo.php
does not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php
, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: