30use Psr\Log\LoggerAwareInterface;
31use Psr\Log\LoggerInterface;
32use Psr\Log\NullLogger;
51 private $titleFormatter;
57 private $loadBalancer;
66 private const MAX_SIZE = 10000;
80 $this->goodLinks =
new MapCacheLRU( self::MAX_SIZE );
81 $this->badLinks =
new MapCacheLRU( self::MAX_SIZE );
83 $this->titleFormatter = $titleFormatter;
84 $this->nsInfo = $nsInfo;
85 $this->loadBalancer = $loadBalancer;
86 $this->logger =
new NullLogger();
92 public function setLogger( LoggerInterface $logger ) {
93 $this->logger = $logger;
102 private function getCacheKey( $page, $passThrough =
false ) {
103 if ( is_string( $page ) ) {
104 if ( $passThrough ) {
107 throw new InvalidArgumentException(
'They key may not be given as a string here' );
111 if ( is_array( $page ) ) {
112 $namespace = $page[
'page_namespace'];
113 $dbkey = $page[
'page_title'];
114 return strtr( $this->titleFormatter->formatTitle( $namespace, $dbkey ),
' ',
'_' );
120 'cross-wiki page reference',
122 'page-wiki' => $page->getWikiId(),
123 'page-reference' => $this->titleFormatter->getFullText( $page )
131 $this->logger->warning(
132 'non-proper page reference: {page-reference}',
133 [
'page-reference' => $this->titleFormatter->getFullText( $page ) ]
139 && ( $page->isExternal() || $page->getText() ===
'' || $page->getNamespace() < 0 )
143 $this->logger->warning(
144 'link to non-proper page: {page-link}',
145 [
'page-link' => $this->titleFormatter->getFullText( $page ) ]
150 return $this->titleFormatter->getPrefixedDBkey( $page );
163 $key = $this->getCacheKey( $page,
true );
165 if ( $key ===
null ) {
169 [ $row ] = $this->goodLinks->get( $key );
171 return $row ? (int)$row->page_id : 0;
187 $key = $this->getCacheKey( $page );
188 if ( $key ===
null ) {
196 [ $row ] = $this->goodLinks->get( $key );
204 return intval( $row->page_id );
206 return intval( $row->page_len );
208 return intval( $row->page_is_redirect );
210 return intval( $row->page_latest );
212 return !empty( $row->page_content_model )
213 ? strval( $row->page_content_model )
216 return !empty( $row->page_lang )
217 ? strval( $row->page_lang )
220 throw new InvalidArgumentException(
"Unknown field: $field" );
234 $key = $this->getCacheKey( $page,
true );
235 if ( $key ===
null ) {
239 return $this->badLinks->has( $key );
258 $revision = 0, $model =
null,
$lang =
null
262 'page_id' => (
int)$id,
263 'page_namespace' => $page->getNamespace(),
264 'page_title' => $page->getDBkey(),
265 'page_len' => (
int)$len,
266 'page_is_redirect' => (
int)$redir,
267 'page_latest' => (
int)$revision,
268 'page_content_model' => $model ? (
string)$model :
null,
271 'page_touched' =>
'',
291 int $queryFlags = IDBAccessObject::READ_NORMAL
293 foreach ( self::getSelectFields() as $field ) {
294 if ( !property_exists( $row, $field ) ) {
295 throw new InvalidArgumentException(
"Missing field: $field" );
299 $key = $this->getCacheKey( $page );
300 if ( $key ===
null ) {
304 $this->goodLinks->set( $key, [ $row, $queryFlags ] );
305 $this->badLinks->clear( $key );
315 $key = $this->getCacheKey( $page );
316 if ( $key !==
null && !$this->
isBadLink( $key ) ) {
317 $this->badLinks->set( $key, 1 );
318 $this->goodLinks->clear( $key );
329 $key = $this->getCacheKey( $page,
true );
331 if ( $key !==
null ) {
332 $this->badLinks->clear( $key );
343 $key = $this->getCacheKey( $page );
345 if ( $key !==
null ) {
346 $this->badLinks->clear( $key );
347 $this->goodLinks->clear( $key );
358 $pageLanguageUseDB = MediaWikiServices::getInstance()->getMainConfig()
359 ->get( MainConfigNames::PageLanguageUseDB );
361 $fields = array_merge(
362 PageStoreRecord::REQUIRED_FIELDS,
365 'page_content_model',
369 if ( $pageLanguageUseDB ) {
370 $fields[] =
'page_lang';
390 public function addLinkObj( $page,
int $queryFlags = IDBAccessObject::READ_NORMAL ) {
392 $page->getNamespace(),
394 [ $this,
'fetchPageRow' ],
398 return $row ? (int)$row->page_id : 0;
409 private function getGoodLinkRowInternal(
411 callable $fetchCallback =
null,
412 int $queryFlags = IDBAccessObject::READ_NORMAL
414 $key = $link ? $this->getCacheKey( $link ) : null;
415 if ( $key ===
null ) {
416 return [
false, false ];
421 $callerShouldAddGoodLink =
false;
423 $forUpdate = $queryFlags & IDBAccessObject::READ_LATEST;
425 if ( !$forUpdate && $this->
isBadLink( $key ) ) {
426 return [ $callerShouldAddGoodLink, false ];
429 [ $row, $rowFlags ] = $this->goodLinks->get( $key );
430 if ( $row && $rowFlags >= $queryFlags ) {
431 return [ $callerShouldAddGoodLink, $row ];
434 if ( !$fetchCallback ) {
435 return [ $callerShouldAddGoodLink, false ];
438 $callerShouldAddGoodLink =
true;
439 if ( $this->usePersistentCache( $ns ) && !$forUpdate ) {
441 $wanCacheKey = $this->wanCache->makeKey(
'page', $ns, sha1( $dbkey ) );
443 $row = $this->wanCache->getWithSetCallback(
445 WANObjectCache::TTL_DAY,
446 function ( $curValue, &$ttl, array &$setOpts ) use ( $fetchCallback, $ns, $dbkey ) {
447 $dbr = $this->loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
448 $setOpts += Database::getCacheSetOptions(
$dbr );
450 $row = $fetchCallback(
$dbr, $ns, $dbkey, [] );
451 $mtime = $row ? (int)
wfTimestamp( TS_UNIX, $row->page_touched ) : false;
452 $ttl = $this->wanCache->adaptiveTTL( $mtime, $ttl );
460 $dbr = $this->loadBalancer->getConnectionRef( $mode );
461 $row = $fetchCallback(
$dbr, $ns, $dbkey, $options );
464 return [ $callerShouldAddGoodLink, $row ];
484 callable $fetchCallback =
null,
485 int $queryFlags = IDBAccessObject::READ_NORMAL
488 if ( $link ===
null ) {
491 [ $shouldAddGoodLink, $row ] = $this->getGoodLinkRowInternal(
498 if ( $shouldAddGoodLink ) {
500 $this->addGoodLinkObjFromRow( $link, $row, $queryFlags );
501 }
catch ( InvalidArgumentException $e ) {
503 $this->invalidateTitle( $link );
504 [ $shouldAddGoodLink, $row ] = $this->getGoodLinkRowInternal(
509 $this->addGoodLinkObjFromRow( $link, $row, $queryFlags );
513 $this->addBadLinkObj( $link );
527 $key = $this->getCacheKey( $page );
529 if ( $key ===
null ) {
533 if ( $this->usePersistentCache( $page ) ) {
534 return [
$cache->makeKey(
'page', $page->getNamespace(), sha1( $page->getDBkey() ) ) ];
545 private function usePersistentCache( $pageOrNamespace ) {
546 $ns = is_int( $pageOrNamespace ) ? $pageOrNamespace : $pageOrNamespace->getNamespace();
551 if ( $this->nsInfo->isContent( $ns ) ) {
555 return ( $ns >= 100 && $this->nsInfo->isSubject( $ns ) );
566 private function fetchPageRow(
IDatabase $db,
int $ns,
string $dbkey, $options = [] ) {
567 $fields = self::getSelectFields();
568 if ( $this->usePersistentCache( $ns ) ) {
569 $fields[] =
'page_touched';
575 [
'page_namespace' => $ns,
'page_title' => $dbkey ],
589 if ( $this->usePersistentCache( $page ) ) {
592 $cache->makeKey(
'page', $page->getNamespace(), sha1( $page->getDBkey() ) )
596 $this->clearLink( $page );
603 $this->goodLinks->clear();
604 $this->badLinks->clear();
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Cache for article titles (prefixed DB keys) and ids linked from one source.
__construct(TitleFormatter $titleFormatter, WANObjectCache $cache, NamespaceInfo $nsInfo, ILoadBalancer $loadBalancer=null)
isBadLink( $page)
Returns true if the fact that this page does not exist had been added to the cache.
invalidateTitle( $page)
Purge the persistent link cache for a title.
addLinkObj( $page, int $queryFlags=IDBAccessObject::READ_NORMAL)
Add a title to the link cache, return the page_id or zero if non-existent.
addGoodLinkObjFromRow( $page, stdClass $row, int $queryFlags=IDBAccessObject::READ_NORMAL)
Same as above with better interface.
addGoodLinkObj( $id, $page, $len=-1, $redir=null, $revision=0, $model=null, $lang=null)
Add information about an existing page to the cache.
getGoodLinkID( $page)
Returns the ID of the given page, if information about this page has been cached.
setLogger(LoggerInterface $logger)
getGoodLinkRow(int $ns, string $dbkey, callable $fetchCallback=null, int $queryFlags=IDBAccessObject::READ_NORMAL)
Returns the row for the page if the page exists (subject to race conditions).
static getSelectFields()
Fields that LinkCache needs to select.
getGoodLinkFieldObj( $page, string $field)
Get a field of a page from the cache.
getMutableCacheKeys(WANObjectCache $cache, $page)
Handles a simple LRU key/value map with a maximum number of entries.
A class containing constants representing the names of configuration variables.
Immutable data record representing an editable page on a wiki.
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a page (or page fragment) title within MediaWiki.
getDBkey()
Get the main part of the link target, in canonical database form.
getNamespace()
Get the namespace index.
Multi-datacenter aware caching interface.
Interface for objects (potentially) representing an editable wiki page.
canExist()
Checks whether this PageIdentity represents a "proper" page, meaning that it could exist as an editab...
if(!isset( $args[0])) $lang