MediaWiki  master
ParserCache.php
Go to the documentation of this file.
1 <?php
25 
30 class ParserCache {
37  const USE_CURRENT_ONLY = 0;
38 
40  const USE_EXPIRED = 1;
41 
43  const USE_OUTDATED = 2;
44 
49  const USE_ANYTHING = 3;
50 
52  private $cache;
53 
59  private $cacheEpoch;
60 
67  public static function singleton() {
68  return MediaWikiServices::getInstance()->getParserCache();
69  }
70 
81  public function __construct( BagOStuff $cache, $cacheEpoch = '20030516000000' ) {
82  $this->cache = $cache;
83  $this->cacheEpoch = $cacheEpoch;
84  }
85 
91  protected function getParserOutputKey( $article, $hash ) {
92  global $wgRequest;
93 
94  // idhash seem to mean 'page id' + 'rendering hash' (r3710)
95  $pageid = $article->getId();
96  $renderkey = (int)( $wgRequest->getVal( 'action' ) == 'render' );
97 
98  $key = $this->cache->makeKey( 'pcache', 'idhash', "{$pageid}-{$renderkey}!{$hash}" );
99  return $key;
100  }
101 
106  protected function getOptionsKey( $page ) {
107  return $this->cache->makeKey( 'pcache', 'idoptions', $page->getId() );
108  }
109 
114  public function deleteOptionsKey( $page ) {
115  $this->cache->delete( $this->getOptionsKey( $page ) );
116  }
117 
132  public function getETag( $article, $popts ) {
133  return 'W/"' . $this->getParserOutputKey( $article,
134  $popts->optionsHash( ParserOptions::allCacheVaryingOptions(), $article->getTitle() ) ) .
135  "--" . $article->getTouched() . '"';
136  }
137 
144  public function getDirty( $article, $popts ) {
145  $value = $this->get( $article, $popts, true );
146  return is_object( $value ) ? $value : false;
147  }
148 
153  private function incrementStats( $article, $metricSuffix ) {
154  // old style global metric (can be removed once no longer used)
155  wfIncrStats( 'pcache.' . $metricSuffix );
156  // new per content model metric
157  $contentModel = str_replace( '.', '_', $article->getContentModel() );
158  $metricSuffix = str_replace( '.', '_', $metricSuffix );
159  wfIncrStats( 'pcache.' . $contentModel . '.' . $metricSuffix );
160  }
161 
182  public function getKey( $article, $popts, $useOutdated = self::USE_ANYTHING ) {
183  if ( is_bool( $useOutdated ) ) {
184  $useOutdated = $useOutdated ? self::USE_ANYTHING : self::USE_CURRENT_ONLY;
185  }
186 
187  if ( $popts instanceof User ) {
188  wfWarn( "Use of outdated prototype ParserCache::getKey( &\$article, &\$user )\n" );
189  $popts = ParserOptions::newFromUser( $popts );
190  }
191 
192  // Determine the options which affect this article
193  $optionsKey = $this->cache->get(
194  $this->getOptionsKey( $article ), BagOStuff::READ_VERIFIED );
195  if ( $optionsKey instanceof CacheTime ) {
196  if ( $useOutdated < self::USE_EXPIRED && $optionsKey->expired( $article->getTouched() ) ) {
197  $this->incrementStats( $article, "miss.expired" );
198  $cacheTime = $optionsKey->getCacheTime();
199  wfDebugLog( "ParserCache",
200  "Parser options key expired, touched " . $article->getTouched()
201  . ", epoch {$this->cacheEpoch}, cached $cacheTime\n" );
202  return false;
203  } elseif ( $useOutdated < self::USE_OUTDATED &&
204  $optionsKey->isDifferentRevision( $article->getLatest() )
205  ) {
206  $this->incrementStats( $article, "miss.revid" );
207  $revId = $article->getLatest();
208  $cachedRevId = $optionsKey->getCacheRevisionId();
209  wfDebugLog( "ParserCache",
210  "ParserOutput key is for an old revision, latest $revId, cached $cachedRevId\n"
211  );
212  return false;
213  }
214 
215  // $optionsKey->mUsedOptions is set by save() by calling ParserOutput::getUsedOptions()
216  $usedOptions = $optionsKey->mUsedOptions;
217  wfDebug( "Parser cache options found.\n" );
218  } else {
219  if ( $useOutdated < self::USE_ANYTHING ) {
220  return false;
221  }
222  $usedOptions = ParserOptions::allCacheVaryingOptions();
223  }
224 
225  return $this->getParserOutputKey(
226  $article,
227  $popts->optionsHash( $usedOptions, $article->getTitle() )
228  );
229  }
230 
241  public function get( $article, $popts, $useOutdated = false ) {
242  $canCache = $article->checkTouched();
243  if ( !$canCache ) {
244  // It's a redirect now
245  return false;
246  }
247 
248  $touched = $article->getTouched();
249 
250  $parserOutputKey = $this->getKey( $article, $popts,
251  $useOutdated ? self::USE_OUTDATED : self::USE_CURRENT_ONLY
252  );
253  if ( $parserOutputKey === false ) {
254  $this->incrementStats( $article, 'miss.absent' );
255  return false;
256  }
257 
258  $casToken = null;
260  $value = $this->cache->get( $parserOutputKey, BagOStuff::READ_VERIFIED );
261  if ( !$value ) {
262  wfDebug( "ParserOutput cache miss.\n" );
263  $this->incrementStats( $article, "miss.absent" );
264  return false;
265  }
266 
267  wfDebug( "ParserOutput cache found.\n" );
268 
269  $wikiPage = method_exists( $article, 'getPage' )
270  ? $article->getPage()
271  : $article;
272 
273  if ( !$useOutdated && $value->expired( $touched ) ) {
274  $this->incrementStats( $article, "miss.expired" );
275  $cacheTime = $value->getCacheTime();
276  wfDebugLog( "ParserCache",
277  "ParserOutput key expired, touched $touched, "
278  . "epoch {$this->cacheEpoch}, cached $cacheTime\n" );
279  $value = false;
280  } elseif ( !$useOutdated && $value->isDifferentRevision( $article->getLatest() ) ) {
281  $this->incrementStats( $article, "miss.revid" );
282  $revId = $article->getLatest();
283  $cachedRevId = $value->getCacheRevisionId();
284  wfDebugLog( "ParserCache",
285  "ParserOutput key is for an old revision, latest $revId, cached $cachedRevId\n"
286  );
287  $value = false;
288  } elseif (
289  Hooks::run( 'RejectParserCacheValue', [ $value, $wikiPage, $popts ] ) === false
290  ) {
291  $this->incrementStats( $article, 'miss.rejected' );
292  wfDebugLog( "ParserCache",
293  "ParserOutput key valid, but rejected by RejectParserCacheValue hook handler.\n"
294  );
295  $value = false;
296  } else {
297  $this->incrementStats( $article, "hit" );
298  }
299 
300  return $value;
301  }
302 
310  public function save(
311  ParserOutput $parserOutput,
312  $page,
313  $popts,
314  $cacheTime = null,
315  $revId = null
316  ) {
317  if ( !$parserOutput->hasText() ) {
318  throw new InvalidArgumentException( 'Attempt to cache a ParserOutput with no text set!' );
319  }
320 
321  $expire = $parserOutput->getCacheExpiry();
322  if ( $expire > 0 && !$this->cache instanceof EmptyBagOStuff ) {
323  $cacheTime = $cacheTime ?: wfTimestampNow();
324  if ( !$revId ) {
325  $revision = $page->getRevision();
326  $revId = $revision ? $revision->getId() : null;
327  }
328 
329  $optionsKey = new CacheTime;
330  $optionsKey->mUsedOptions = $parserOutput->getUsedOptions();
331  $optionsKey->updateCacheExpiry( $expire );
332 
333  $optionsKey->setCacheTime( $cacheTime );
334  $parserOutput->setCacheTime( $cacheTime );
335  $optionsKey->setCacheRevisionId( $revId );
336  $parserOutput->setCacheRevisionId( $revId );
337 
338  $parserOutputKey = $this->getParserOutputKey( $page,
339  $popts->optionsHash( $optionsKey->mUsedOptions, $page->getTitle() ) );
340 
341  // Save the timestamp so that we don't have to load the revision row on view
342  $parserOutput->setTimestamp( $page->getTimestamp() );
343 
344  $msg = "Saved in parser cache with key $parserOutputKey" .
345  " and timestamp $cacheTime" .
346  " and revision id $revId" .
347  "\n";
348 
349  $parserOutput->mText .= "\n<!-- $msg -->\n";
350  wfDebug( $msg );
351 
352  // Save the parser output
353  $this->cache->set(
354  $parserOutputKey,
355  $parserOutput,
356  $expire,
358  );
359 
360  // ...and its pointer
361  $this->cache->set( $this->getOptionsKey( $page ), $optionsKey, $expire );
362 
363  Hooks::run(
364  'ParserCacheSaveComplete',
365  [ $this, $parserOutput, $page->getTitle(), $popts, $revId ]
366  );
367  } elseif ( $expire <= 0 ) {
368  wfDebug( "Parser output was marked as uncacheable and has not been saved.\n" );
369  }
370  }
371 
379  public function getCacheStorage() {
380  return $this->cache;
381  }
382 }
__construct(BagOStuff $cache, $cacheEpoch='20030516000000')
Setup a cache pathway with a given back-end storage mechanism.
Definition: ParserCache.php:81
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
getOptionsKey( $page)
setCacheTime( $t)
setCacheTime() sets the timestamp expressing when the page has been rendered.
Definition: CacheTime.php:74
const USE_EXPIRED
Use expired data if current data is unavailable.
Definition: ParserCache.php:40
Parser cache specific expiry check.
Definition: CacheTime.php:29
getDirty( $article, $popts)
Retrieve the ParserOutput from ParserCache, even if it&#39;s outdated.
incrementStats( $article, $metricSuffix)
getETag( $article, $popts)
Provides an E-Tag suitable for the whole page.
const USE_ANYTHING
Use expired data and data from different revisions, and if all else fails vary on all variable option...
Definition: ParserCache.php:49
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
hasText()
Returns true if text was passed to the constructor, or set using setText().
getCacheExpiry()
Returns the number of seconds after which this object should expire.
Definition: CacheTime.php:129
wfIncrStats( $key, $count=1)
Increment a statistics counter.
BagOStuff $cache
Definition: ParserCache.php:52
const READ_VERIFIED
Definition: BagOStuff.php:80
deleteOptionsKey( $page)
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
setCacheRevisionId( $id)
Definition: CacheTime.php:95
getKey( $article, $popts, $useOutdated=self::USE_ANYTHING)
Generates a key for caching the given article considering the given parser options.
static singleton()
Get an instance of this object.
Definition: ParserCache.php:67
static allCacheVaryingOptions()
Return all option keys that vary the options hash.
const WRITE_ALLOW_SEGMENTS
Definition: BagOStuff.php:84
static newFromUser( $user)
Get a ParserOptions object from a given user.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
getCacheStorage()
Get the backend BagOStuff instance that powers the parser cache.
const USE_CURRENT_ONLY
Constants for self::getKey()
Definition: ParserCache.php:37
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:728
setTimestamp( $timestamp)
const USE_OUTDATED
Use expired data or data from different revisions if current data is unavailable. ...
Definition: ParserCache.php:43
save(ParserOutput $parserOutput, $page, $popts, $cacheTime=null, $revId=null)
string $cacheEpoch
Anything cached prior to this is invalidated.
Definition: ParserCache.php:59
return true
Definition: router.php:92
getUsedOptions()
Returns the options from its ParserOptions which have been taken into account to produce this output...
getParserOutputKey( $article, $hash)
Definition: ParserCache.php:91
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200