MediaWiki  REL1_23
ParserCache.php
Go to the documentation of this file.
00001 <?php
00028 class ParserCache {
00029     private $mMemc;
00035     public static function singleton() {
00036         static $instance;
00037         if ( !isset( $instance ) ) {
00038             global $parserMemc;
00039             $instance = new ParserCache( $parserMemc );
00040         }
00041         return $instance;
00042     }
00043 
00051     protected function __construct( $memCached ) {
00052         if ( !$memCached ) {
00053             throw new MWException( "Tried to create a ParserCache with an invalid memcached" );
00054         }
00055         $this->mMemc = $memCached;
00056     }
00057 
00063     protected function getParserOutputKey( $article, $hash ) {
00064         global $wgRequest;
00065 
00066         // idhash seem to mean 'page id' + 'rendering hash' (r3710)
00067         $pageid = $article->getID();
00068         $renderkey = (int)( $wgRequest->getVal( 'action' ) == 'render' );
00069 
00070         $key = wfMemcKey( 'pcache', 'idhash', "{$pageid}-{$renderkey}!{$hash}" );
00071         return $key;
00072     }
00073 
00078     protected function getOptionsKey( $article ) {
00079         $pageid = $article->getID();
00080         return wfMemcKey( 'pcache', 'idoptions', "{$pageid}" );
00081     }
00082 
00097     function getETag( $article, $popts ) {
00098         return 'W/"' . $this->getParserOutputKey( $article,
00099             $popts->optionsHash( ParserOptions::legacyOptions(), $article->getTitle() ) ) .
00100                 "--" . $article->getTouched() . '"';
00101     }
00102 
00109     public function getDirty( $article, $popts ) {
00110         $value = $this->get( $article, $popts, true );
00111         return is_object( $value ) ? $value : false;
00112     }
00113 
00133     public function getKey( $article, $popts, $useOutdated = true ) {
00134         global $wgCacheEpoch;
00135 
00136         if ( $popts instanceof User ) {
00137             wfWarn( "Use of outdated prototype ParserCache::getKey( &\$article, &\$user )\n" );
00138             $popts = ParserOptions::newFromUser( $popts );
00139         }
00140 
00141         // Determine the options which affect this article
00142         $optionsKey = $this->mMemc->get( $this->getOptionsKey( $article ) );
00143         if ( $optionsKey != false ) {
00144             if ( !$useOutdated && $optionsKey->expired( $article->getTouched() ) ) {
00145                 wfIncrStats( "pcache_miss_expired" );
00146                 $cacheTime = $optionsKey->getCacheTime();
00147                 wfDebug( "Parser options key expired, touched " . $article->getTouched() . ", epoch $wgCacheEpoch, cached $cacheTime\n" );
00148                 return false;
00149             } elseif ( $optionsKey->isDifferentRevision( $article->getLatest() ) ) {
00150                 wfIncrStats( "pcache_miss_revid" );
00151                 $revId = $article->getLatest();
00152                 $cachedRevId = $optionsKey->getCacheRevisionId();
00153                 wfDebug( "ParserOutput key is for an old revision, latest $revId, cached $cachedRevId\n" );
00154                 return false;
00155             }
00156 
00157             // $optionsKey->mUsedOptions is set by save() by calling ParserOutput::getUsedOptions()
00158             $usedOptions = $optionsKey->mUsedOptions;
00159             wfDebug( "Parser cache options found.\n" );
00160         } else {
00161             if ( !$useOutdated ) {
00162                 return false;
00163             }
00164             $usedOptions = ParserOptions::legacyOptions();
00165         }
00166 
00167         return $this->getParserOutputKey( $article, $popts->optionsHash( $usedOptions, $article->getTitle() ) );
00168     }
00169 
00180     public function get( $article, $popts, $useOutdated = false ) {
00181         global $wgCacheEpoch;
00182         wfProfileIn( __METHOD__ );
00183 
00184         $canCache = $article->checkTouched();
00185         if ( !$canCache ) {
00186             // It's a redirect now
00187             wfProfileOut( __METHOD__ );
00188             return false;
00189         }
00190 
00191         $touched = $article->getTouched();
00192 
00193         $parserOutputKey = $this->getKey( $article, $popts, $useOutdated );
00194         if ( $parserOutputKey === false ) {
00195             wfIncrStats( 'pcache_miss_absent' );
00196             wfProfileOut( __METHOD__ );
00197             return false;
00198         }
00199 
00200         $value = $this->mMemc->get( $parserOutputKey );
00201         if ( !$value ) {
00202             wfDebug( "ParserOutput cache miss.\n" );
00203             wfIncrStats( "pcache_miss_absent" );
00204             wfProfileOut( __METHOD__ );
00205             return false;
00206         }
00207 
00208         wfDebug( "ParserOutput cache found.\n" );
00209 
00210         // The edit section preference may not be the appropiate one in
00211         // the ParserOutput, as we are not storing it in the parsercache
00212         // key. Force it here. See bug 31445.
00213         $value->setEditSectionTokens( $popts->getEditSection() );
00214 
00215         if ( !$useOutdated && $value->expired( $touched ) ) {
00216             wfIncrStats( "pcache_miss_expired" );
00217             $cacheTime = $value->getCacheTime();
00218             wfDebug( "ParserOutput key expired, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
00219             $value = false;
00220         } elseif ( $value->isDifferentRevision( $article->getLatest() ) ) {
00221             wfIncrStats( "pcache_miss_revid" );
00222             $revId = $article->getLatest();
00223             $cachedRevId = $value->getCacheRevisionId();
00224             wfDebug( "ParserOutput key is for an old revision, latest $revId, cached $cachedRevId\n" );
00225             $value = false;
00226         } else {
00227             wfIncrStats( "pcache_hit" );
00228         }
00229 
00230         wfProfileOut( __METHOD__ );
00231         return $value;
00232     }
00233 
00241     public function save( $parserOutput, $page, $popts, $cacheTime = null, $revId = null ) {
00242         $expire = $parserOutput->getCacheExpiry();
00243         if ( $expire > 0 ) {
00244             $cacheTime = $cacheTime ?: wfTimestampNow();
00245             if ( !$revId ) {
00246                 $revision = $page->getRevision();
00247                 $revId = $revision ? $revision->getId() : null;
00248             }
00249 
00250             $optionsKey = new CacheTime;
00251             $optionsKey->mUsedOptions = $parserOutput->getUsedOptions();
00252             $optionsKey->updateCacheExpiry( $expire );
00253 
00254             $optionsKey->setCacheTime( $cacheTime );
00255             $parserOutput->setCacheTime( $cacheTime );
00256             $optionsKey->setCacheRevisionId( $revId );
00257             $parserOutput->setCacheRevisionId( $revId );
00258 
00259             $optionsKey->setContainsOldMagic( $parserOutput->containsOldMagic() );
00260 
00261             $parserOutputKey = $this->getParserOutputKey( $page,
00262                 $popts->optionsHash( $optionsKey->mUsedOptions, $page->getTitle() ) );
00263 
00264             // Save the timestamp so that we don't have to load the revision row on view
00265             $parserOutput->setTimestamp( $page->getTimestamp() );
00266 
00267             $msg = "Saved in parser cache with key $parserOutputKey" .
00268                 " and timestamp $cacheTime" .
00269                 " and revision id $revId" .
00270                 "\n";
00271 
00272             $parserOutput->mText .= "\n<!-- $msg -->\n";
00273             wfDebug( $msg );
00274 
00275             // Save the parser output
00276             $this->mMemc->set( $parserOutputKey, $parserOutput, $expire );
00277 
00278             // ...and its pointer
00279             $this->mMemc->set( $this->getOptionsKey( $page ), $optionsKey, $expire );
00280         } else {
00281             wfDebug( "Parser output was marked as uncacheable and has not been saved.\n" );
00282         }
00283     }
00284 }