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
63 public function __construct(
64 iterable $arr,
65 private LinkCache $linkCache,
66 private TitleFormatter $titleFormatter,
67 private Language $contentLanguage,
68 private GenderCache $genderCache,
69 private IConnectionProvider $dbProvider,
70 private LinksMigration $linksMigration,
71 private TempUserDetailsLookup $tempUserDetailsLookup,
72 private LinkAlwaysKnownLookup $linkAlwaysKnownLookup,
73 private LoggerInterface $logger
74 ) {
75 foreach ( $arr as $item ) {
76 $this->addObj( $item );
77 }
78 }
79
88 public function setCaller( $caller ): self {
89 $this->caller = $caller;
90 return $this;
91 }
92
101 public function addUser( UserIdentity $user ): void {
102 $this->users[$user->getName()] = $user;
103
104 $this->add( NS_USER, $user->getName() );
105 $this->add( NS_USER_TALK, $user->getName() );
106 }
107
111 public function addObj( $link ) {
112 if ( !$link ) {
113 // Don't die if we got null, just skip. There is nothing to do anyway.
114 // For now, let's avoid things like T282180. We should be more strict in the future.
115 $this->logger->warning(
116 'Skipping null link, probably due to a bad title.',
117 [ 'exception' => new RuntimeException() ]
118 );
119 return;
120 }
121 if ( $link instanceof LinkTarget && $link->isExternal() ) {
122 $this->logger->warning(
123 'Skipping interwiki link',
124 [ 'exception' => new RuntimeException() ]
125 );
126 return;
127 }
128
129 Assert::parameterType( [ LinkTarget::class, PageReference::class ], $link, '$link' );
130 $this->add( $link->getNamespace(), $link->getDBkey() );
131 }
132
137 public function add( $ns, $dbkey ) {
138 if ( $ns < 0 || $dbkey === '' ) {
139 // T137083
140 return;
141 }
142 $this->data[$ns][strtr( $dbkey, ' ', '_' )] = 1;
143 }
144
152 public function setArray( $array ) {
153 $this->data = $array;
154 }
155
161 public function isEmpty() {
162 return $this->getSize() == 0;
163 }
164
170 public function getSize() {
171 return count( $this->data );
172 }
173
179 public function execute() {
180 return $this->executeInto( $this->linkCache );
181 }
182
190 public function getPageIdentities(): array {
191 if ( $this->pageIdentities === null ) {
192 $this->execute();
193 }
194
195 return $this->pageIdentities;
196 }
197
204 protected function executeInto( $cache ) {
205 $res = $this->doQuery();
206 $this->doGenderQuery();
207 $this->doIsAlwaysKnownQuery();
208
209 // Prefetch expiration status for temporary accounts added to this batch via addUser()
210 // for efficient user link rendering (T358469).
211 if ( count( $this->users ) > 0 ) {
212 $this->tempUserDetailsLookup->preloadExpirationStatus( $this->users );
213 }
214
215 return $this->addResultToCache( $cache, $res );
216 }
217
229 public function addResultToCache( $cache, $res ) {
230 if ( !$res ) {
231 return [];
232 }
233
234 $this->pageIdentities ??= [];
235
236 $ids = [];
237 $remaining = $this->data;
238
239 // For each returned entry, add it to the list of good links, and remove it from $remaining
240 foreach ( $res as $row ) {
241 try {
242 $title = new TitleValue( (int)$row->page_namespace, $row->page_title );
243
244 $cache->addGoodLinkObjFromRow( $title, $row );
245 $pdbk = $this->titleFormatter->getPrefixedDBkey( $title );
246 $ids[$pdbk] = $row->page_id;
247
248 $pageIdentity = PageIdentityValue::localIdentity(
249 (int)$row->page_id,
250 (int)$row->page_namespace,
251 $row->page_title
252 );
253
254 $key = CacheKeyHelper::getKeyForPage( $pageIdentity );
255 $this->pageIdentities[$key] = $pageIdentity;
256 } catch ( InvalidArgumentException ) {
257 $this->logger->warning(
258 'Encountered invalid title',
259 [ 'title_namespace' => $row->page_namespace, 'title_dbkey' => $row->page_title ]
260 );
261 }
262
263 unset( $remaining[$row->page_namespace][$row->page_title] );
264 }
265
266 // The remaining links in $data are bad links, register them as such
267 foreach ( $remaining as $ns => $dbkeys ) {
268 foreach ( $dbkeys as $dbkey => $unused ) {
269 try {
270 $title = new TitleValue( (int)$ns, (string)$dbkey );
271
272 $cache->addBadLinkObj( $title );
273 $pdbk = $this->titleFormatter->getPrefixedDBkey( $title );
274 $ids[$pdbk] = 0;
275
276 $pageIdentity = PageIdentityValue::localIdentity( 0, (int)$ns, $dbkey );
277 $key = CacheKeyHelper::getKeyForPage( $pageIdentity );
278 $this->pageIdentities[$key] = $pageIdentity;
279 } catch ( InvalidArgumentException ) {
280 $this->logger->warning(
281 'Encountered invalid title',
282 [ 'title_namespace' => $ns, 'title_dbkey' => $dbkey ]
283 );
284 }
285 }
286 }
287
288 return $ids;
289 }
290
296 public function doQuery() {
297 if ( $this->isEmpty() ) {
298 return false;
299 }
300
301 $caller = __METHOD__;
302 if ( strval( $this->caller ) !== '' ) {
303 $caller .= " (for {$this->caller})";
304 }
305
306 // This is similar to LinkHolderArray::replaceInternal
307 $dbr = $this->dbProvider->getReplicaDatabase();
308 return $dbr->newSelectQueryBuilder()
309 ->select( LinkCache::getSelectFields() )
310 ->from( 'page' )
311 ->where( $this->constructSet( 'page', $dbr ) )
312 ->caller( $caller )
313 ->fetchResultSet();
314 }
315
321 public function doGenderQuery() {
322 if ( $this->isEmpty() || !$this->contentLanguage->needsGenderDistinction() ) {
323 return false;
324 }
325
326 $this->genderCache->doLinkBatch( $this->data, $this->caller );
327
328 return true;
329 }
330
331 public function doIsAlwaysKnownQuery(): void {
332 if ( $this->isEmpty() ) {
333 return;
334 }
335
336 $links = [];
337 foreach ( $this->data as $ns => $dbkeys ) {
338 foreach ( $dbkeys as $dbkey => $_ ) {
339 try {
340 $links[] = new TitleValue( (int)$ns, (string)$dbkey );
341 } catch ( InvalidArgumentException ) {
342 $this->logger->warning(
343 'Encountered invalid title',
344 [
345 'title_namespace' => $ns,
346 'title_dbkey' => $dbkey,
347 'exception' => new RuntimeException
348 ]
349 );
350 }
351 }
352 }
353
354 $this->linkAlwaysKnownLookup->preload( $links );
355 }
356
368 public function constructSet( $prefix, $db ) {
369 if ( isset( $this->linksMigration::$prefixToTableMapping[$prefix] ) ) {
370 [ $blNamespace, $blTitle ] = $this->linksMigration->getTitleFields(
371 $this->linksMigration::$prefixToTableMapping[$prefix]
372 );
373 } else {
374 $blNamespace = "{$prefix}_namespace";
375 $blTitle = "{$prefix}_title";
376 }
377 return $db->makeWhereFrom2d( $this->data, $blNamespace, $blTitle );
378 }
379}
380
382class_alias( LinkBatch::class, 'LinkBatch' );
383
385class_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:71
Look up "gender" user preference.
Base class for language-specific code.
Definition Language.php:65
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.
Definition LinkBatch.php:88
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.
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
__construct(iterable $arr, private LinkCache $linkCache, private TitleFormatter $titleFormatter, private Language $contentLanguage, private GenderCache $genderCache, private IConnectionProvider $dbProvider, private LinksMigration $linksMigration, private TempUserDetailsLookup $tempUserDetailsLookup, private LinkAlwaysKnownLookup $linkAlwaysKnownLookup, private LoggerInterface $logger)
Definition LinkBatch.php:63
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.