MediaWiki REL1_39
ParserOutputAccess.php
Go to the documentation of this file.
1<?php
20namespace MediaWiki\Page;
21
23use InvalidArgumentException;
24use MediaWiki\Logger\Spi as LoggerSpi;
29use ParserCache;
31use ParserOutput;
36use Status;
38use Wikimedia\Assert\Assert;
40
51
55 public const OPT_NO_CHECK_CACHE = 1;
56
58 public const OPT_FORCE_PARSE = self::OPT_NO_CHECK_CACHE;
59
63 public 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
100 private $localCache = [];
101
103 private $revisionLookup;
104
106 private $revisionRenderer;
107
109 private $statsDataFactory;
110
112 private $lbFactory;
113
115 private $loggerSpi;
116
118 private $wikiPageFactory;
119
121 private $titleFormatter;
122
134 public function __construct(
135 ParserCache $primaryCache,
136 RevisionOutputCache $secondaryCache,
137 RevisionLookup $revisionLookup,
138 RevisionRenderer $revisionRenderer,
139 IBufferingStatsdDataFactory $statsDataFactory,
140 ILBFactory $lbFactory,
141 LoggerSpi $loggerSpi,
142 WikiPageFactory $wikiPageFactory,
143 TitleFormatter $titleFormatter
144 ) {
145 $this->primaryCache = $primaryCache;
146 $this->secondaryCache = $secondaryCache;
147 $this->revisionLookup = $revisionLookup;
148 $this->revisionRenderer = $revisionRenderer;
149 $this->statsDataFactory = $statsDataFactory;
150 $this->lbFactory = $lbFactory;
151 $this->loggerSpi = $loggerSpi;
152 $this->wikiPageFactory = $wikiPageFactory;
153 $this->titleFormatter = $titleFormatter;
154 }
155
164 private function shouldUseCache(
165 PageRecord $page,
166 ?RevisionRecord $rev
167 ) {
168 if ( $rev && !$rev->getId() ) {
169 // The revision isn't from the database, so the output can't safely be cached.
170 return self::CACHE_NONE;
171 }
172
173 // NOTE: Keep in sync with ParserWikiPage::shouldCheckParserCache().
174 // NOTE: when we allow caching of old revisions in the future,
175 // we must not allow caching of deleted revisions.
176
177 $wikiPage = $this->wikiPageFactory->newFromTitle( $page );
178 if ( !$page->exists() || !$wikiPage->getContentHandler()->isParserCacheSupported() ) {
179 return self::CACHE_NONE;
180 }
181
182 $isOld = $rev && $rev->getId() !== $page->getLatest();
183 if ( !$isOld ) {
184 return self::CACHE_PRIMARY;
185 }
186
187 if ( !$rev->audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) {
188 // deleted/suppressed revision
189 return self::CACHE_NONE;
190 }
191
192 return self::CACHE_SECONDARY;
193 }
194
205 public function getCachedParserOutput(
206 PageRecord $page,
207 ParserOptions $parserOptions,
208 ?RevisionRecord $revision = null,
209 int $options = 0
210 ): ?ParserOutput {
211 $isOld = $revision && $revision->getId() !== $page->getLatest();
212 $useCache = $this->shouldUseCache( $page, $revision );
213 $classCacheKey = $this->primaryCache->makeParserOutputKey( $page, $parserOptions );
214
215 if ( $useCache === self::CACHE_PRIMARY ) {
216 if ( isset( $this->localCache[$classCacheKey] ) && !$isOld ) {
217 return $this->localCache[$classCacheKey];
218 }
219 $output = $this->primaryCache->get( $page, $parserOptions );
220 } elseif ( $useCache === self::CACHE_SECONDARY && $revision ) {
221 $output = $this->secondaryCache->get( $revision, $parserOptions );
222 } else {
223 $output = null;
224 }
225
226 if ( $output && !$isOld ) {
227 $this->localCache[$classCacheKey] = $output;
228 }
229
230 if ( $output ) {
231 $this->statsDataFactory->increment( "ParserOutputAccess.Cache.$useCache.hit" );
232 } else {
233 $this->statsDataFactory->increment( "ParserOutputAccess.Cache.$useCache.miss" );
234 }
235
236 return $output ?: null; // convert false to null
237 }
238
261 public function getParserOutput(
262 PageRecord $page,
263 ParserOptions $parserOptions,
264 ?RevisionRecord $revision = null,
265 int $options = 0
266 ): Status {
267 $error = $this->checkPreconditions( $page, $revision, $options );
268 if ( $error ) {
269 $this->statsDataFactory->increment( "ParserOutputAccess.Case.error" );
270 return $error;
271 }
272
273 $isOld = $revision && $revision->getId() !== $page->getLatest();
274 if ( $isOld ) {
275 $this->statsDataFactory->increment( 'ParserOutputAccess.Case.old' );
276 } else {
277 $this->statsDataFactory->increment( 'ParserOutputAccess.Case.current' );
278 }
279
280 if ( !( $options & self::OPT_NO_CHECK_CACHE ) ) {
281 $output = $this->getCachedParserOutput( $page, $parserOptions, $revision );
282 if ( $output ) {
283 return Status::newGood( $output );
284 }
285 }
286
287 if ( !$revision ) {
288 $revId = $page->getLatest();
289 $revision = $revId ? $this->revisionLookup->getRevisionById( $revId ) : null;
290
291 if ( !$revision ) {
292 $this->statsDataFactory->increment( "ParserOutputAccess.Status.norev" );
293 return Status::newFatal( 'missing-revision', $revId );
294 }
295 }
296
297 $work = $this->newPoolWorkArticleView( $page, $parserOptions, $revision, $options );
299 $status = $work->execute();
300 $output = $status->getValue();
301 Assert::postcondition( $output || !$status->isOK(), 'Worker returned invalid status' );
302
303 if ( $output && !$isOld ) {
304 $classCacheKey = $this->primaryCache->makeParserOutputKey( $page, $parserOptions );
305 $this->localCache[$classCacheKey] = $output;
306 }
307
308 if ( $status->isGood() ) {
309 $this->statsDataFactory->increment( 'ParserOutputAccess.Status.good' );
310 } elseif ( $status->isOK() ) {
311 $this->statsDataFactory->increment( 'ParserOutputAccess.Status.ok' );
312 } else {
313 $this->statsDataFactory->increment( 'ParserOutputAccess.Status.error' );
314 }
315
316 return $status;
317 }
318
326 private function checkPreconditions(
327 PageRecord $page,
328 ?RevisionRecord $revision = null,
329 int $options = 0
330 ): ?Status {
331 if ( !$page->exists() ) {
332 return Status::newFatal( 'nopagetext' );
333 }
334
335 if ( !( $options & self::OPT_NO_UPDATE_CACHE ) && $revision && !$revision->getId() ) {
336 throw new InvalidArgumentException(
337 'The revision does not have a known ID. Use NO_CACHE.'
338 );
339 }
340
341 if ( $revision && $revision->getPageId() !== $page->getId() ) {
342 throw new InvalidArgumentException(
343 'The revision does not belong to the given page.'
344 );
345 }
346
347 if ( $revision && !( $options & self::OPT_NO_AUDIENCE_CHECK ) ) {
348 // NOTE: If per-user checks are desired, the caller should perform them and
349 // then set OPT_NO_AUDIENCE_CHECK if they passed.
350 if ( !$revision->audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_PUBLIC ) ) {
351 return Status::newFatal(
352 'missing-revision-permission',
353 $revision->getId(),
354 $revision->getTimestamp(),
355 $this->titleFormatter->getPrefixedDBkey( $page )
356 );
357 }
358 }
359
360 return null;
361 }
362
371 private function newPoolWorkArticleView(
372 PageRecord $page,
373 ParserOptions $parserOptions,
374 RevisionRecord $revision,
375 int $options
376 ): PoolCounterWork {
377 $useCache = $this->shouldUseCache( $page, $revision );
378
379 switch ( $useCache ) {
380 case self::CACHE_PRIMARY:
381 $this->statsDataFactory->increment( 'ParserOutputAccess.PoolWork.Current' );
382 $parserCacheMetadata = $this->primaryCache->getMetadata( $page );
383 $cacheKey = $this->primaryCache->makeParserOutputKey( $page, $parserOptions,
384 $parserCacheMetadata ? $parserCacheMetadata->getUsedOptions() : null
385 );
386
387 $workKey = $cacheKey . ':revid:' . $revision->getId();
388
390 $workKey,
391 $page,
392 $revision,
393 $parserOptions,
394 $this->revisionRenderer,
395 $this->primaryCache,
396 $this->lbFactory,
397 $this->loggerSpi,
398 $this->wikiPageFactory,
399 !( $options & self::OPT_NO_UPDATE_CACHE )
400 );
401
402 case self::CACHE_SECONDARY:
403 $this->statsDataFactory->increment( 'ParserOutputAccess.PoolWork.Old' );
404 $workKey = $this->secondaryCache->makeParserOutputKey( $revision, $parserOptions );
405 return new PoolWorkArticleViewOld(
406 $workKey,
407 $this->secondaryCache,
408 $revision,
409 $parserOptions,
410 $this->revisionRenderer,
411 $this->loggerSpi
412 );
413
414 default:
415 $this->statsDataFactory->increment( 'ParserOutputAccess.PoolWork.Uncached' );
416 $workKey = $this->secondaryCache->makeParserOutputKeyOptionalRevId( $revision, $parserOptions );
417 return new PoolWorkArticleView(
418 $workKey,
419 $revision,
420 $parserOptions,
421 $this->revisionRenderer,
422 $this->loggerSpi
423 );
424 }
425
426 // unreachable
427 }
428
429}
const CACHE_NONE
Definition Defines.php:86
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Definition WebStart.php:82
Service for getting rendered output of a given page.
__construct(ParserCache $primaryCache, RevisionOutputCache $secondaryCache, RevisionLookup $revisionLookup, RevisionRenderer $revisionRenderer, IBufferingStatsdDataFactory $statsDataFactory, ILBFactory $lbFactory, LoggerSpi $loggerSpi, WikiPageFactory $wikiPageFactory, TitleFormatter $titleFormatter)
getCachedParserOutput(PageRecord $page, ParserOptions $parserOptions, ?RevisionRecord $revision=null, int $options=0)
Returns the rendered output for the given page if it is present in the cache.
getParserOutput(PageRecord $page, ParserOptions $parserOptions, ?RevisionRecord $revision=null, int $options=0)
Returns the rendered output for the given page.
Service for creating WikiPage objects.
Cache for ParserOutput objects.
Page revision base class.
audienceCan( $field, $audience, Authority $performer=null)
Check that the given audience has access to the given field.
getId( $wikiId=self::LOCAL)
Get revision ID.
The RevisionRenderer service provides access to rendered output for revisions.
Cache for ParserOutput objects corresponding to the latest page revisions.
Set options of the Parser.
Class for dealing with PoolCounters using class members.
PoolWorkArticleView for the current revision of a page, using ParserCache.
PoolWorkArticleView for an old revision of a page, using a simple cache.
PoolCounter protected work wrapping RenderedRevision->getRevisionParserOutput.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
MediaWiki adaptation of StatsdDataFactory that provides buffering functionality.
Service provider interface for \Psr\Log\LoggerInterface implementation libraries.
Definition Spi.php:38
exists()
Checks if the page currently exists.
Data record representing a page that is (or used to be, or could be) an editable page on a wiki.
getLatest( $wikiId=self::LOCAL)
The ID of the page's latest revision.
getId( $wikiId=self::LOCAL)
Returns the page ID.
Service for looking up page revisions.
A title formatter service for MediaWiki.
Manager of ILoadBalancer objects, and indirectly of IDatabase connections.