MediaWiki master
LinkBatch.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Page;
8
9use InvalidArgumentException;
18use Psr\Log\LoggerInterface;
19use RuntimeException;
20use Wikimedia\Assert\Assert;
24
36class LinkBatch {
40 public $data = [];
41
45 private array $users = [];
46
50 private $pageIdentities = null;
51
55 protected $caller;
56
60 private $linkCache;
61
65 private $titleFormatter;
66
70 private $contentLanguage;
71
75 private $genderCache;
76
80 private $dbProvider;
81
83 private $linksMigration;
84
85 private TempUserDetailsLookup $tempUserDetailsLookup;
86
88 private $logger;
89
104 public function __construct(
105 iterable $arr,
106 LinkCache $linkCache,
107 TitleFormatter $titleFormatter,
108 Language $contentLanguage,
109 GenderCache $genderCache,
110 IConnectionProvider $dbProvider,
111 LinksMigration $linksMigration,
112 TempUserDetailsLookup $tempUserDetailsLookup,
113 LoggerInterface $logger
114 ) {
115 $this->linkCache = $linkCache;
116 $this->titleFormatter = $titleFormatter;
117 $this->contentLanguage = $contentLanguage;
118 $this->genderCache = $genderCache;
119 $this->dbProvider = $dbProvider;
120 $this->linksMigration = $linksMigration;
121 $this->tempUserDetailsLookup = $tempUserDetailsLookup;
122 $this->logger = $logger;
123
124 foreach ( $arr as $item ) {
125 $this->addObj( $item );
126 }
127 }
128
137 public function setCaller( $caller ): self {
138 $this->caller = $caller;
139 return $this;
140 }
141
150 public function addUser( UserIdentity $user ): void {
151 $this->users[$user->getName()] = $user;
152
153 $this->add( NS_USER, $user->getName() );
154 $this->add( NS_USER_TALK, $user->getName() );
155 }
156
160 public function addObj( $link ) {
161 if ( !$link ) {
162 // Don't die if we got null, just skip. There is nothing to do anyway.
163 // For now, let's avoid things like T282180. We should be more strict in the future.
164 $this->logger->warning(
165 'Skipping null link, probably due to a bad title.',
166 [ 'exception' => new RuntimeException() ]
167 );
168 return;
169 }
170 if ( $link instanceof LinkTarget && $link->isExternal() ) {
171 $this->logger->warning(
172 'Skipping interwiki link',
173 [ 'exception' => new RuntimeException() ]
174 );
175 return;
176 }
177
178 Assert::parameterType( [ LinkTarget::class, PageReference::class ], $link, '$link' );
179 $this->add( $link->getNamespace(), $link->getDBkey() );
180 }
181
186 public function add( $ns, $dbkey ) {
187 if ( $ns < 0 || $dbkey === '' ) {
188 // T137083
189 return;
190 }
191 $this->data[$ns][strtr( $dbkey, ' ', '_' )] = 1;
192 }
193
201 public function setArray( $array ) {
202 $this->data = $array;
203 }
204
210 public function isEmpty() {
211 return $this->getSize() == 0;
212 }
213
219 public function getSize() {
220 return count( $this->data );
221 }
222
228 public function execute() {
229 return $this->executeInto( $this->linkCache );
230 }
231
239 public function getPageIdentities(): array {
240 if ( $this->pageIdentities === null ) {
241 $this->execute();
242 }
243
244 return $this->pageIdentities;
245 }
246
253 protected function executeInto( $cache ) {
254 $res = $this->doQuery();
255 $this->doGenderQuery();
256
257 // Prefetch expiration status for temporary accounts added to this batch via addUser()
258 // for efficient user link rendering (T358469).
259 if ( count( $this->users ) > 0 ) {
260 $this->tempUserDetailsLookup->preloadExpirationStatus( $this->users );
261 }
262
263 return $this->addResultToCache( $cache, $res );
264 }
265
277 public function addResultToCache( $cache, $res ) {
278 if ( !$res ) {
279 return [];
280 }
281
282 $this->pageIdentities ??= [];
283
284 $ids = [];
285 $remaining = $this->data;
286
287 // For each returned entry, add it to the list of good links, and remove it from $remaining
288 foreach ( $res as $row ) {
289 try {
290 $title = new TitleValue( (int)$row->page_namespace, $row->page_title );
291
292 $cache->addGoodLinkObjFromRow( $title, $row );
293 $pdbk = $this->titleFormatter->getPrefixedDBkey( $title );
294 $ids[$pdbk] = $row->page_id;
295
296 $pageIdentity = PageIdentityValue::localIdentity(
297 (int)$row->page_id,
298 (int)$row->page_namespace,
299 $row->page_title
300 );
301
302 $key = CacheKeyHelper::getKeyForPage( $pageIdentity );
303 $this->pageIdentities[$key] = $pageIdentity;
304 } catch ( InvalidArgumentException ) {
305 $this->logger->warning(
306 'Encountered invalid title',
307 [ 'title_namespace' => $row->page_namespace, 'title_dbkey' => $row->page_title ]
308 );
309 }
310
311 unset( $remaining[$row->page_namespace][$row->page_title] );
312 }
313
314 // The remaining links in $data are bad links, register them as such
315 foreach ( $remaining as $ns => $dbkeys ) {
316 foreach ( $dbkeys as $dbkey => $unused ) {
317 try {
318 $title = new TitleValue( (int)$ns, (string)$dbkey );
319
320 $cache->addBadLinkObj( $title );
321 $pdbk = $this->titleFormatter->getPrefixedDBkey( $title );
322 $ids[$pdbk] = 0;
323
324 $pageIdentity = PageIdentityValue::localIdentity( 0, (int)$ns, $dbkey );
325 $key = CacheKeyHelper::getKeyForPage( $pageIdentity );
326 $this->pageIdentities[$key] = $pageIdentity;
327 } catch ( InvalidArgumentException ) {
328 $this->logger->warning(
329 'Encountered invalid title',
330 [ 'title_namespace' => $ns, 'title_dbkey' => $dbkey ]
331 );
332 }
333 }
334 }
335
336 return $ids;
337 }
338
344 public function doQuery() {
345 if ( $this->isEmpty() ) {
346 return false;
347 }
348
349 $caller = __METHOD__;
350 if ( strval( $this->caller ) !== '' ) {
351 $caller .= " (for {$this->caller})";
352 }
353
354 // This is similar to LinkHolderArray::replaceInternal
355 $dbr = $this->dbProvider->getReplicaDatabase();
356 return $dbr->newSelectQueryBuilder()
357 ->select( LinkCache::getSelectFields() )
358 ->from( 'page' )
359 ->where( $this->constructSet( 'page', $dbr ) )
360 ->caller( $caller )
361 ->fetchResultSet();
362 }
363
369 public function doGenderQuery() {
370 if ( $this->isEmpty() || !$this->contentLanguage->needsGenderDistinction() ) {
371 return false;
372 }
373
374 $this->genderCache->doLinkBatch( $this->data, $this->caller );
375
376 return true;
377 }
378
390 public function constructSet( $prefix, $db ) {
391 if ( isset( $this->linksMigration::$prefixToTableMapping[$prefix] ) ) {
392 [ $blNamespace, $blTitle ] = $this->linksMigration->getTitleFields(
393 $this->linksMigration::$prefixToTableMapping[$prefix]
394 );
395 } else {
396 $blNamespace = "{$prefix}_namespace";
397 $blTitle = "{$prefix}_title";
398 }
399 return $db->makeWhereFrom2d( $this->data, $blNamespace, $blTitle );
400 }
401}
402
404class_alias( LinkBatch::class, 'LinkBatch' );
405
407class_alias( LinkBatch::class, 'MediaWiki\Cache\LinkBatch' );
const NS_USER
Definition Defines.php:53
const NS_USER_TALK
Definition Defines.php:54
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:68
Look up "gender" user preference.
Base class for language-specific code.
Definition Language.php:69
Service for compat reading of links tables.
Batch query for page metadata and feed to LinkCache.
Definition LinkBatch.php:36
getPageIdentities()
Do the query, add the results to the LinkCache object, and return ProperPageIdentity instances corres...
addResultToCache( $cache, $res)
Add a database result with page rows to the LinkCache.
setCaller( $caller)
Set the function name to attribute database queries to, in debug logs.
doQuery()
Perform the existence test query.
getSize()
Return the size of the batch.
array< int, array< string, mixed > > $data
2-d array, first index namespace, second index dbkey, value arbitrary
Definition LinkBatch.php:40
addUser(UserIdentity $user)
Add user page and user talk page for a given user to this batch.
__construct(iterable $arr, LinkCache $linkCache, TitleFormatter $titleFormatter, Language $contentLanguage, GenderCache $genderCache, IConnectionProvider $dbProvider, LinksMigration $linksMigration, TempUserDetailsLookup $tempUserDetailsLookup, LoggerInterface $logger)
setArray( $array)
Replace the link batch with a given 2-d array.
constructSet( $prefix, $db)
Construct a WHERE clause which will match all the given titles.
isEmpty()
Whether no pages have been added.
string null $caller
For debugging which method is using this class.
Definition LinkBatch.php:55
execute()
Do the query and add the results to the LinkCache.
executeInto( $cache)
Do the query and add the results to a given LinkCache object.
doGenderQuery()
Execute and cache {{GENDER:...}} information for user pages in this LinkBatch.
Page existence and metadata cache.
Definition LinkCache.php:52
A title formatter service for MediaWiki.
Represents the target of a wiki link.
Caching lookup service for metadata related to temporary accounts, such as expiration.
Represents the target of a wiki link.
isExternal()
Whether this LinkTarget has an interwiki component.
Interface for a page that is (or could be, or used to be) an editable wiki page.
Interface for objects representing user identity.
Provide primary and replica IDatabase connections.
Result wrapper for grabbing data queried from an IDatabase object.
Interface for query language.