MediaWiki master
LinkBatch.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Cache;
25
26use InvalidArgumentException;
35use Psr\Log\LoggerInterface;
36use RuntimeException;
37use Wikimedia\Assert\Assert;
41
48class LinkBatch {
52 public $data = [];
53
57 private $pageIdentities = null;
58
62 protected $caller;
63
67 private $linkCache;
68
72 private $titleFormatter;
73
77 private $contentLanguage;
78
82 private $genderCache;
83
87 private $dbProvider;
88
90 private $linksMigration;
91
93 private $logger;
94
108 public function __construct(
109 iterable $arr,
110 LinkCache $linkCache,
111 TitleFormatter $titleFormatter,
112 Language $contentLanguage,
113 GenderCache $genderCache,
114 IConnectionProvider $dbProvider,
115 LinksMigration $linksMigration,
116 LoggerInterface $logger
117 ) {
118 $this->linkCache = $linkCache;
119 $this->titleFormatter = $titleFormatter;
120 $this->contentLanguage = $contentLanguage;
121 $this->genderCache = $genderCache;
122 $this->dbProvider = $dbProvider;
123 $this->linksMigration = $linksMigration;
124 $this->logger = $logger;
125
126 foreach ( $arr as $item ) {
127 $this->addObj( $item );
128 }
129 }
130
139 public function setCaller( $caller ) {
140 $this->caller = $caller;
141
142 return $this;
143 }
144
148 public function addObj( $link ) {
149 if ( !$link ) {
150 // Don't die if we got null, just skip. There is nothing to do anyway.
151 // For now, let's avoid things like T282180. We should be more strict in the future.
152 $this->logger->warning(
153 'Skipping null link, probably due to a bad title.',
154 [ 'exception' => new RuntimeException() ]
155 );
156 return;
157 }
158 if ( $link instanceof LinkTarget && $link->isExternal() ) {
159 $this->logger->warning(
160 'Skipping interwiki link',
161 [ 'exception' => new RuntimeException() ]
162 );
163 return;
164 }
165
166 Assert::parameterType( [ LinkTarget::class, PageReference::class ], $link, '$link' );
167 $this->add( $link->getNamespace(), $link->getDBkey() );
168 }
169
174 public function add( $ns, $dbkey ) {
175 if ( $ns < 0 || $dbkey === '' ) {
176 // T137083
177 return;
178 }
179 $this->data[$ns][strtr( $dbkey, ' ', '_' )] = 1;
180 }
181
188 public function setArray( $array ) {
189 $this->data = $array;
190 }
191
197 public function isEmpty() {
198 return $this->getSize() == 0;
199 }
200
206 public function getSize() {
207 return count( $this->data );
208 }
209
215 public function execute() {
216 return $this->executeInto( $this->linkCache );
217 }
218
226 public function getPageIdentities(): array {
227 if ( $this->pageIdentities === null ) {
228 $this->execute();
229 }
230
231 return $this->pageIdentities;
232 }
233
241 protected function executeInto( $cache ) {
242 $res = $this->doQuery();
243 $this->doGenderQuery();
244 return $this->addResultToCache( $cache, $res );
245 }
246
257 public function addResultToCache( $cache, $res ) {
258 if ( !$res ) {
259 return [];
260 }
261
262 // For each returned entry, add it to the list of good links, and remove it from $remaining
263
264 $this->pageIdentities ??= [];
265
266 $ids = [];
267 $remaining = $this->data;
268 foreach ( $res as $row ) {
269 try {
270 $title = new TitleValue( (int)$row->page_namespace, $row->page_title );
271
272 $cache->addGoodLinkObjFromRow( $title, $row );
273 $pdbk = $this->titleFormatter->getPrefixedDBkey( $title );
274 $ids[$pdbk] = $row->page_id;
275
276 $pageIdentity = new PageIdentityValue(
277 (int)$row->page_id,
278 (int)$row->page_namespace,
279 $row->page_title,
280 ProperPageIdentity::LOCAL
281 );
282
283 $key = CacheKeyHelper::getKeyForPage( $pageIdentity );
284 $this->pageIdentities[$key] = $pageIdentity;
285 } catch ( InvalidArgumentException $ex ) {
286 $this->logger->warning(
287 'Encountered invalid title',
288 [ 'title_namespace' => $row->page_namespace, 'title_dbkey' => $row->page_title ]
289 );
290 }
291
292 unset( $remaining[$row->page_namespace][$row->page_title] );
293 }
294
295 // The remaining links in $data are bad links, register them as such
296 foreach ( $remaining as $ns => $dbkeys ) {
297 foreach ( $dbkeys as $dbkey => $unused ) {
298 try {
299 $title = new TitleValue( (int)$ns, (string)$dbkey );
300
301 $cache->addBadLinkObj( $title );
302 $pdbk = $this->titleFormatter->getPrefixedDBkey( $title );
303 $ids[$pdbk] = 0;
304
305 $pageIdentity = new PageIdentityValue( 0, (int)$ns, $dbkey, ProperPageIdentity::LOCAL );
306 $key = CacheKeyHelper::getKeyForPage( $pageIdentity );
307 $this->pageIdentities[$key] = $pageIdentity;
308 } catch ( InvalidArgumentException $ex ) {
309 $this->logger->warning(
310 'Encountered invalid title',
311 [ 'title_namespace' => $ns, 'title_dbkey' => $dbkey ]
312 );
313 }
314 }
315 }
316
317 return $ids;
318 }
319
324 public function doQuery() {
325 if ( $this->isEmpty() ) {
326 return false;
327 }
328
329 // This is similar to LinkHolderArray::replaceInternal
330 $dbr = $this->dbProvider->getReplicaDatabase();
331 $queryBuilder = $dbr->newSelectQueryBuilder()
332 ->select( LinkCache::getSelectFields() )
333 ->from( 'page' )
334 ->where( $this->constructSet( 'page', $dbr ) );
335
336 $caller = __METHOD__;
337 if ( strval( $this->caller ) !== '' ) {
338 $caller .= " (for {$this->caller})";
339 }
340
341 return $queryBuilder->caller( $caller )->fetchResultSet();
342 }
343
349 public function doGenderQuery() {
350 if ( $this->isEmpty() || !$this->contentLanguage->needsGenderDistinction() ) {
351 return false;
352 }
353
354 $this->genderCache->doLinkBatch( $this->data, $this->caller );
355
356 return true;
357 }
358
370 public function constructSet( $prefix, $db ) {
371 if ( isset( $this->linksMigration::$prefixToTableMapping[$prefix] ) ) {
372 [ $blNamespace, $blTitle ] = $this->linksMigration->getTitleFields(
373 $this->linksMigration::$prefixToTableMapping[$prefix]
374 );
375 } else {
376 $blNamespace = "{$prefix}_namespace";
377 $blTitle = "{$prefix}_title";
378 }
379 return $db->makeWhereFrom2d( $this->data, $blNamespace, $blTitle );
380 }
381}
382
384class_alias( LinkBatch::class, 'LinkBatch' );
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
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:48
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:52
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.
__construct(iterable $arr, LinkCache $linkCache, TitleFormatter $titleFormatter, Language $contentLanguage, GenderCache $genderCache, IConnectionProvider $dbProvider, LinksMigration $linksMigration, LoggerInterface $logger)
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)
Use ->setCaller( METHOD ) to indicate which code is using this class.
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.
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:62
Cache for article titles (prefixed DB keys) and ids linked from one source.
Definition LinkCache.php:52
static getSelectFields()
Fields that LinkCache needs to select.
Base class for language-specific code.
Definition Language.php:80
Service for compat reading of links tables.
Immutable value object representing a page identity.
Represents the target of a wiki link.
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.
A title formatter service for MediaWiki.
Provide primary and replica IDatabase connections.
Result wrapper for grabbing data queried from an IDatabase object.
Interface for query language.