MediaWiki master
LinkBatch.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Cache;
25
26use InvalidArgumentException;
37use Psr\Log\LoggerInterface;
38use RuntimeException;
39use Wikimedia\Assert\Assert;
43
50class LinkBatch {
54 public $data = [];
55
59 private array $users = [];
60
64 private $pageIdentities = null;
65
69 protected $caller;
70
74 private $linkCache;
75
79 private $titleFormatter;
80
84 private $contentLanguage;
85
89 private $genderCache;
90
94 private $dbProvider;
95
97 private $linksMigration;
98
99 private TempUserDetailsLookup $tempUserDetailsLookup;
100
102 private $logger;
103
118 public function __construct(
119 iterable $arr,
120 LinkCache $linkCache,
121 TitleFormatter $titleFormatter,
122 Language $contentLanguage,
123 GenderCache $genderCache,
124 IConnectionProvider $dbProvider,
125 LinksMigration $linksMigration,
126 TempUserDetailsLookup $tempUserDetailsLookup,
127 LoggerInterface $logger
128 ) {
129 $this->linkCache = $linkCache;
130 $this->titleFormatter = $titleFormatter;
131 $this->contentLanguage = $contentLanguage;
132 $this->genderCache = $genderCache;
133 $this->dbProvider = $dbProvider;
134 $this->linksMigration = $linksMigration;
135 $this->tempUserDetailsLookup = $tempUserDetailsLookup;
136 $this->logger = $logger;
137
138 foreach ( $arr as $item ) {
139 $this->addObj( $item );
140 }
141 }
142
151 public function setCaller( $caller ): self {
152 $this->caller = $caller;
153
154 return $this;
155 }
156
164 public function addUser( UserIdentity $user ): void {
165 $this->users[$user->getName()] = $user;
166
167 $this->add( NS_USER, $user->getName() );
168 $this->add( NS_USER_TALK, $user->getName() );
169 }
170
174 public function addObj( $link ) {
175 if ( !$link ) {
176 // Don't die if we got null, just skip. There is nothing to do anyway.
177 // For now, let's avoid things like T282180. We should be more strict in the future.
178 $this->logger->warning(
179 'Skipping null link, probably due to a bad title.',
180 [ 'exception' => new RuntimeException() ]
181 );
182 return;
183 }
184 if ( $link instanceof LinkTarget && $link->isExternal() ) {
185 $this->logger->warning(
186 'Skipping interwiki link',
187 [ 'exception' => new RuntimeException() ]
188 );
189 return;
190 }
191
192 Assert::parameterType( [ LinkTarget::class, PageReference::class ], $link, '$link' );
193 $this->add( $link->getNamespace(), $link->getDBkey() );
194 }
195
200 public function add( $ns, $dbkey ) {
201 if ( $ns < 0 || $dbkey === '' ) {
202 // T137083
203 return;
204 }
205 $this->data[$ns][strtr( $dbkey, ' ', '_' )] = 1;
206 }
207
214 public function setArray( $array ) {
215 $this->data = $array;
216 }
217
223 public function isEmpty() {
224 return $this->getSize() == 0;
225 }
226
232 public function getSize() {
233 return count( $this->data );
234 }
235
241 public function execute() {
242 return $this->executeInto( $this->linkCache );
243 }
244
252 public function getPageIdentities(): array {
253 if ( $this->pageIdentities === null ) {
254 $this->execute();
255 }
256
257 return $this->pageIdentities;
258 }
259
267 protected function executeInto( $cache ) {
268 $res = $this->doQuery();
269 $this->doGenderQuery();
270
271 // Prefetch expiration status for temporary accounts added to this batch via addUser()
272 // for efficient user link rendering (T358469).
273 if ( count( $this->users ) > 0 ) {
274 $this->tempUserDetailsLookup->preloadExpirationStatus( $this->users );
275 }
276
277 return $this->addResultToCache( $cache, $res );
278 }
279
290 public function addResultToCache( $cache, $res ) {
291 if ( !$res ) {
292 return [];
293 }
294
295 // For each returned entry, add it to the list of good links, and remove it from $remaining
296
297 $this->pageIdentities ??= [];
298
299 $ids = [];
300 $remaining = $this->data;
301 foreach ( $res as $row ) {
302 try {
303 $title = new TitleValue( (int)$row->page_namespace, $row->page_title );
304
305 $cache->addGoodLinkObjFromRow( $title, $row );
306 $pdbk = $this->titleFormatter->getPrefixedDBkey( $title );
307 $ids[$pdbk] = $row->page_id;
308
309 $pageIdentity = PageIdentityValue::localIdentity(
310 (int)$row->page_id,
311 (int)$row->page_namespace,
312 $row->page_title
313 );
314
315 $key = CacheKeyHelper::getKeyForPage( $pageIdentity );
316 $this->pageIdentities[$key] = $pageIdentity;
317 } catch ( InvalidArgumentException ) {
318 $this->logger->warning(
319 'Encountered invalid title',
320 [ 'title_namespace' => $row->page_namespace, 'title_dbkey' => $row->page_title ]
321 );
322 }
323
324 unset( $remaining[$row->page_namespace][$row->page_title] );
325 }
326
327 // The remaining links in $data are bad links, register them as such
328 foreach ( $remaining as $ns => $dbkeys ) {
329 foreach ( $dbkeys as $dbkey => $unused ) {
330 try {
331 $title = new TitleValue( (int)$ns, (string)$dbkey );
332
333 $cache->addBadLinkObj( $title );
334 $pdbk = $this->titleFormatter->getPrefixedDBkey( $title );
335 $ids[$pdbk] = 0;
336
337 $pageIdentity = PageIdentityValue::localIdentity( 0, (int)$ns, $dbkey );
338 $key = CacheKeyHelper::getKeyForPage( $pageIdentity );
339 $this->pageIdentities[$key] = $pageIdentity;
340 } catch ( InvalidArgumentException ) {
341 $this->logger->warning(
342 'Encountered invalid title',
343 [ 'title_namespace' => $ns, 'title_dbkey' => $dbkey ]
344 );
345 }
346 }
347 }
348
349 return $ids;
350 }
351
356 public function doQuery() {
357 if ( $this->isEmpty() ) {
358 return false;
359 }
360
361 $caller = __METHOD__;
362 if ( strval( $this->caller ) !== '' ) {
363 $caller .= " (for {$this->caller})";
364 }
365
366 // This is similar to LinkHolderArray::replaceInternal
367 $dbr = $this->dbProvider->getReplicaDatabase();
368 return $dbr->newSelectQueryBuilder()
369 ->select( LinkCache::getSelectFields() )
370 ->from( 'page' )
371 ->where( $this->constructSet( 'page', $dbr ) )
372 ->caller( $caller )
373 ->fetchResultSet();
374 }
375
381 public function doGenderQuery() {
382 if ( $this->isEmpty() || !$this->contentLanguage->needsGenderDistinction() ) {
383 return false;
384 }
385
386 $this->genderCache->doLinkBatch( $this->data, $this->caller );
387
388 return true;
389 }
390
402 public function constructSet( $prefix, $db ) {
403 if ( isset( $this->linksMigration::$prefixToTableMapping[$prefix] ) ) {
404 [ $blNamespace, $blTitle ] = $this->linksMigration->getTitleFields(
405 $this->linksMigration::$prefixToTableMapping[$prefix]
406 );
407 } else {
408 $blNamespace = "{$prefix}_namespace";
409 $blTitle = "{$prefix}_title";
410 }
411 return $db->makeWhereFrom2d( $this->data, $blNamespace, $blTitle );
412 }
413}
414
416class_alias( LinkBatch::class, 'LinkBatch' );
const NS_USER
Definition Defines.php:67
const NS_USER_TALK
Definition Defines.php:68
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:82
Look up "gender" user preference.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:50
addResultToCache( $cache, $res)
Add a result wrapper containing IDs and titles to a LinkCache object.
isEmpty()
Returns true if no pages have been added, false otherwise.
array< int, array< string, mixed > > $data
2-d array, first index namespace, second index dbkey, value arbitrary
Definition LinkBatch.php:54
addUser(UserIdentity $user)
Convenience function to add user and user talk pages for a given user to this batch.
execute()
Do the query and add the results to the LinkCache object.
executeInto( $cache)
Do the query and add the results to a given LinkCache object Return an array mapping PDBK to ID.
constructSet( $prefix, $db)
Construct a WHERE clause which will match all the given titles.
getPageIdentities()
Do the query, add the results to the LinkCache object, and return ProperPageIdentity instances corres...
setCaller( $caller)
Set the function name to attribute database queries to, in debug logs.
getSize()
Returns the size of the batch.
doQuery()
Perform the existence test query, return a result wrapper with page_id fields.
doGenderQuery()
Do (and cache) {{GENDER:...}} information for userpages in this LinkBatch.
__construct(iterable $arr, LinkCache $linkCache, TitleFormatter $titleFormatter, Language $contentLanguage, GenderCache $genderCache, IConnectionProvider $dbProvider, LinksMigration $linksMigration, TempUserDetailsLookup $tempUserDetailsLookup, LoggerInterface $logger)
setArray( $array)
Set the link list to a given 2-d array First key is the namespace, second is the DB key,...
string null $caller
For debugging which method is using this class.
Definition LinkBatch.php:69
Cache for article titles (prefixed DB keys) and ids linked from one source.
Definition LinkCache.php:52
Base class for language-specific code.
Definition Language.php:81
Service for compat reading of links tables.
Immutable value object representing a page identity.
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 objects (potentially) representing a page that can be viewable and linked to on a wiki.
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.