MediaWiki  master
ParserOutputAccess.php
Go to the documentation of this file.
1 <?php
2 
22 namespace MediaWiki\Page;
23 
25 use InvalidArgumentException;
26 use MediaWiki\Logger\Spi as LoggerSpi;
30 use ParserCache;
31 use ParserOptions;
32 use ParserOutput;
36 use Status;
38 use WikiPage;
39 
50 
54  public const OPT_NO_CHECK_CACHE = 1;
55 
57  public const OPT_FORCE_PARSE = self::OPT_NO_CHECK_CACHE;
58 
63  private const OPT_NO_UPDATE_CACHE = 2;
64 
70  public const OPT_NO_AUDIENCE_CHECK = 4;
71 
76  public const OPT_NO_CACHE = self::OPT_NO_UPDATE_CACHE | self::OPT_NO_CHECK_CACHE;
77 
79  private const CACHE_NONE = 'none';
80 
82  private const CACHE_PRIMARY = 'primary';
83 
85  private const CACHE_SECONDARY = 'secondary';
86 
88  private $primaryCache;
89 
93  private $secondaryCache;
94 
97 
100 
102  private $lbFactory;
103 
105  private $loggerSpi;
106 
115  public function __construct(
117  RevisionOutputCache $secondaryCache,
118  RevisionRenderer $revisionRenderer,
120  ILBFactory $lbFactory,
121  LoggerSpi $loggerSpi
122  ) {
123  $this->primaryCache = $primaryCache;
124  $this->secondaryCache = $secondaryCache;
125  $this->revisionRenderer = $revisionRenderer;
126  $this->statsDataFactory = $statsDataFactory;
127  $this->lbFactory = $lbFactory;
128  $this->loggerSpi = $loggerSpi;
129  }
130 
140  private function shouldUseCache(
141  WikiPage $page,
142  ParserOptions $parserOptions,
143  ?RevisionRecord $rev
144  ) {
145  if ( $rev && !$rev->getId() ) {
146  // The revision isn't from the database, so the output can't safely be cached.
147  return self::CACHE_NONE;
148  }
149 
150  // NOTE: Keep in sync with ParserWikiPage::shouldCheckParserCache().
151  // NOTE: when we allow caching of old revisions in the future,
152  // we must not allow caching of deleted revisions.
153 
154  if ( !$page->exists() || !$page->getContentHandler()->isParserCacheSupported() ) {
155  return self::CACHE_NONE;
156  }
157 
158  if ( !$rev || $rev->getId() === $page->getLatest() ) {
159  // current revision
160  return self::CACHE_PRIMARY;
161  }
162 
163  if ( !$rev->audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) {
164  // deleted/suppressed revision
165  return self::CACHE_NONE;
166  }
167 
168  return self::CACHE_SECONDARY;
169  }
170 
181  public function getCachedParserOutput(
182  WikiPage $page,
183  ParserOptions $parserOptions,
184  ?RevisionRecord $revision = null,
185  int $options = 0
186  ): ?ParserOutput {
187  $useCache = $this->shouldUseCache( $page, $parserOptions, $revision );
188 
189  if ( $useCache === self::CACHE_PRIMARY ) {
190  $output = $this->primaryCache->get( $page, $parserOptions );
191  } elseif ( $useCache === self::CACHE_SECONDARY && $revision ) {
192  $output = $this->secondaryCache->get( $revision, $parserOptions );
193  } else {
194  $output = null;
195  }
196 
197  $hitOrMiss = $output ? 'hit' : 'miss';
198  $this->statsDataFactory->increment( "ParserOutputAccess.Cache.$useCache.$hitOrMiss" );
199 
200  return $output ?: null; // convert false to null
201  }
202 
225  public function getParserOutput(
226  WikiPage $page,
227  ParserOptions $parserOptions,
228  ?RevisionRecord $revision = null,
229  int $options = 0
230  ): Status {
231  $error = $this->checkPreconditions( $page, $revision, $options );
232  if ( $error ) {
233  $this->statsDataFactory->increment( "ParserOutputAccess.Case.error" );
234  return $error;
235  }
236 
237  $currentOrOld = ( $revision && $revision->getId() !== $page->getLatest() ) ? 'old' : 'current';
238  $this->statsDataFactory->increment( "ParserOutputAccess.Case.$currentOrOld" );
239 
240  if ( !( $options & self::OPT_NO_CHECK_CACHE ) ) {
241  $output = $this->getCachedParserOutput( $page, $parserOptions, $revision );
242  if ( $output ) {
243  return Status::newGood( $output );
244  }
245  }
246 
247  if ( !$revision ) {
248  $revision = $page->getRevisionRecord();
249 
250  if ( !$revision ) {
251  $this->statsDataFactory->increment( "ParserOutputAccess.Status.norev" );
252  return Status::newFatal(
253  'missing-revision',
254  $page->getLatest()
255  );
256  }
257  }
258 
259  $work = $this->newPoolWorkArticleView( $page, $parserOptions, $revision, $options );
260  $work->execute();
261  $output = $work->getParserOutput();
262 
263  $status = Status::newGood();
264  if ( $work->getError() ) {
265  $status->merge( $work->getError() );
266  }
267 
268  if ( !$output && $status->isOK() ) {
269  // TODO: PoolWorkArticle should properly report errors (T267610)
270  $status->fatal( 'pool-errorunknown' );
271  }
272 
273  if ( $output && $status->isOK() ) {
274  $status->setResult( true, $output );
275  }
276 
277  if ( $status->isOK() && $work->getIsDirty() ) {
278  $staleReason = $work->getIsFastStale()
279  ? 'view-pool-contention' : 'view-pool-overload';
280 
281  $status->warning( 'view-pool-dirty-output' );
282  $status->warning( $staleReason );
283  }
284 
285  if ( $status->isGood() ) {
286  $statusMsg = 'good';
287  } elseif ( $status->isOK() ) {
288  $statusMsg = 'ok';
289  } else {
290  $statusMsg = 'error';
291  }
292 
293  $this->statsDataFactory->increment( "ParserOutputAccess.Status.$statusMsg" );
294  return $status;
295  }
296 
304  private function checkPreconditions(
305  WikiPage $page,
306  ?RevisionRecord $revision = null,
307  int $options = 0
308  ): ?Status {
309  if ( !$page->exists() ) {
310  return Status::newFatal( 'nopagetext' );
311  }
312 
313  if ( !( $options & self::OPT_NO_UPDATE_CACHE ) && $revision && !$revision->getId() ) {
314  throw new InvalidArgumentException(
315  'The revision does not have a known ID. Use NO_CACHE.'
316  );
317  }
318 
319  if ( $revision && $revision->getPageId() !== $page->getId() ) {
320  throw new InvalidArgumentException(
321  'The revision does not belong to the given page.'
322  );
323  }
324 
325  if ( $revision && !( $options & self::OPT_NO_AUDIENCE_CHECK ) ) {
326  // NOTE: If per-user checks are desired, the caller should perform them and
327  // then set OPT_NO_AUDIENCE_CHECK if they passed.
328  if ( !$revision->audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) {
329  return Status::newFatal(
330  'missing-revision-permission',
331  $revision->getId(),
332  $revision->getTimestamp(),
333  $page->getTitle()->getPrefixedDBkey()
334  );
335  }
336  }
337 
338  return null;
339  }
340 
349  private function newPoolWorkArticleView(
350  WikiPage $page,
351  ParserOptions $parserOptions,
352  RevisionRecord $revision,
353  int $options
355  if ( $options & self::OPT_NO_UPDATE_CACHE ) {
356  $useCache = self::CACHE_NONE;
357  } else {
358  $useCache = $this->shouldUseCache( $page, $parserOptions, $revision );
359  }
360 
361  switch ( $useCache ) {
362  case self::CACHE_PRIMARY:
363  $this->statsDataFactory->increment( 'ParserOutputAccess.PoolWork.Current' );
364  $parserCacheMetadata = $this->primaryCache->getMetadata( $page );
365  $cacheKey = $this->primaryCache->makeParserOutputKey( $page, $parserOptions,
366  $parserCacheMetadata ? $parserCacheMetadata->getUsedOptions() : null
367  );
368 
369  $workKey = $cacheKey . ':revid:' . $revision->getId();
370 
371  return new PoolWorkArticleViewCurrent(
372  $workKey,
373  $page,
374  $revision,
375  $parserOptions,
376  $this->revisionRenderer,
377  $this->primaryCache,
378  $this->lbFactory,
379  $this->loggerSpi
380  );
381 
382  case $useCache == self::CACHE_SECONDARY:
383  $this->statsDataFactory->increment( 'ParserOutputAccess.PoolWork.Old' );
384  $workKey = $this->secondaryCache->makeParserOutputKey( $revision, $parserOptions );
385  return new PoolWorkArticleViewOld(
386  $workKey,
387  $this->secondaryCache,
388  $revision,
389  $parserOptions,
390  $this->revisionRenderer,
391  $this->loggerSpi
392  );
393 
394  default:
395  $this->statsDataFactory->increment( 'ParserOutputAccess.PoolWork.Uncached' );
396  $workKey = $this->secondaryCache->makeParserOutputKey( $revision, $parserOptions );
397  return new PoolWorkArticleView(
398  $workKey,
399  $revision,
400  $parserOptions,
401  $this->revisionRenderer,
402  $this->loggerSpi
403  );
404  }
405 
406  // unreachable
407  }
408 
409 }
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:44
Page\ParserOutputAccess\$secondaryCache
RevisionOutputCache $secondaryCache
Definition: ParserOutputAccess.php:93
PoolWorkArticleViewCurrent
PoolWorkArticleView for the current revision of a page, using ParserCache.
Definition: PoolWorkArticleViewCurrent.php:31
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
WikiPage\getRevisionRecord
getRevisionRecord()
Get the latest revision.
Definition: WikiPage.php:789
ParserOutput
Definition: ParserOutput.php:31
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
Page\ParserOutputAccess
Service for getting rendered output of a given page.
Definition: ParserOutputAccess.php:49
Page\ParserOutputAccess\getParserOutput
getParserOutput(WikiPage $page, ParserOptions $parserOptions, ?RevisionRecord $revision=null, int $options=0)
Returns the rendered output for the given page.
Definition: ParserOutputAccess.php:225
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:55
PoolWorkArticleView
PoolCounter protected work wrapping RenderedRevision->getRevisionParserOutput.
Definition: PoolWorkArticleView.php:34
Page\ParserOutputAccess\$loggerSpi
LoggerSpi $loggerSpi
Definition: ParserOutputAccess.php:105
Page\ParserOutputAccess\$lbFactory
ILBFactory $lbFactory
Definition: ParserOutputAccess.php:102
PoolWorkArticleViewOld
PoolWorkArticleView for an old revision of a page, using a simple cache.
Definition: PoolWorkArticleViewOld.php:30
Page\ParserOutputAccess\$statsDataFactory
IBufferingStatsdDataFactory $statsDataFactory
Definition: ParserOutputAccess.php:99
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
Page\ParserOutputAccess\$primaryCache
ParserCache $primaryCache
Definition: ParserOutputAccess.php:84
Page\ParserOutputAccess\shouldUseCache
shouldUseCache(WikiPage $page, ParserOptions $parserOptions, ?RevisionRecord $rev)
Use a cache?
Definition: ParserOutputAccess.php:140
WikiPage\getId
getId()
Definition: WikiPage.php:567
WikiPage\getTitle
getTitle()
Get the title object of the article.
Definition: WikiPage.php:295
WikiPage\exists
exists()
Definition: WikiPage.php:577
Page\ParserOutputAccess\$revisionRenderer
RevisionRenderer $revisionRenderer
Definition: ParserOutputAccess.php:96
WikiPage\getLatest
getLatest()
Get the page_latest field.
Definition: WikiPage.php:691
Revision\RevisionRenderer
The RevisionRenderer service provides access to rendered output for revisions.
Definition: RevisionRenderer.php:45
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
Page\ParserOutputAccess\checkPreconditions
checkPreconditions(WikiPage $page, ?RevisionRecord $revision=null, int $options=0)
Definition: ParserOutputAccess.php:304
IBufferingStatsdDataFactory
MediaWiki adaptation of StatsdDataFactory that provides buffering functionality.
Definition: IBufferingStatsdDataFactory.php:13
Parser\RevisionOutputCache
Cache for ParserOutput objects.
Definition: RevisionOutputCache.php:44
Revision\RevisionRecord\FOR_PUBLIC
const FOR_PUBLIC
Definition: RevisionRecord.php:60
Page\ParserOutputAccess\__construct
__construct(ParserCache $primaryCache, RevisionOutputCache $secondaryCache, RevisionRenderer $revisionRenderer, IBufferingStatsdDataFactory $statsDataFactory, ILBFactory $lbFactory, LoggerSpi $loggerSpi)
Definition: ParserOutputAccess.php:115
Revision\RevisionRecord\DELETED_TEXT
const DELETED_TEXT
Definition: RevisionRecord.php:51
ParserCache
Cache for ParserOutput objects corresponding to the latest page revisions.
Definition: ParserCache.php:61
MediaWiki\Page
Definition: ContentModelChangeFactory.php:23
Page\ParserOutputAccess\getCachedParserOutput
getCachedParserOutput(WikiPage $page, ParserOptions $parserOptions, ?RevisionRecord $revision=null, int $options=0)
Returns the rendered output for the given page if it is present in the cache.
Definition: ParserOutputAccess.php:181
MediaWiki\Logger\Spi
Service provider interface for \Psr\Log\LoggerInterface implementation libraries.
Definition: Spi.php:38
WikiPage\getContentHandler
getContentHandler()
Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
Definition: WikiPage.php:286
Page\ParserOutputAccess\newPoolWorkArticleView
newPoolWorkArticleView(WikiPage $page, ParserOptions $parserOptions, RevisionRecord $revision, int $options)
Definition: ParserOutputAccess.php:349
Wikimedia\Rdbms\ILBFactory
An interface for generating database load balancers.
Definition: ILBFactory.php:33
CACHE_NONE
const CACHE_NONE
Definition: Defines.php:85