wiki.techinc.nl/includes/poolcounter/PoolWorkArticleViewOld.php
daniel a2ae4192c0 ParserOutputAccess: cache ouput for old revisions
DEPLOY: Set $wgOldRevisionParserCacheExpireTime = 0 in production first!

Bug: T267832
Depends-On: I3c73f5d9f6a54e2736600e8f9506659a3fb0e7f6
Change-Id: I0fe275b4991f1bf89c7bb587132bc4fb0ea862e2
2020-11-17 20:52:35 +00:00

171 lines
4.8 KiB
PHP

<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
use MediaWiki\Json\JsonUnserializer;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionRenderer;
use Psr\Log\LoggerInterface;
use Wikimedia\Assert\Assert;
/**
* PoolWorkArticleView for an old revision of a page, using a simple cache.
*
* @internal
*/
class PoolWorkArticleViewOld extends PoolWorkArticleView {
/** @var int */
private $cacheExpiry;
/** @var BagOStuff */
private $cache;
/** @var string */
private $cacheKey;
/** @var JsonUnserializer */
private $jsonUnserializer;
/** @var LoggerInterface */
private $logger;
/**
* @param string $cacheKey Key for the ParserOutput to use in $cache.
* Also used as the PoolCounter key.
* @param int $cacheExpiry Expiry for ParserOutput in $cache.
* @param BagOStuff $cache The cache to store ParserOutput in.
* @param RevisionRecord $revision Revision to render
* @param ParserOptions $parserOptions ParserOptions to use for the parse
* @param RevisionRenderer $revisionRenderer
* @param JsonUnserializer $jsonUnserializer
*/
public function __construct(
string $cacheKey,
int $cacheExpiry,
BagOStuff $cache,
RevisionRecord $revision,
ParserOptions $parserOptions,
RevisionRenderer $revisionRenderer,
JsonUnserializer $jsonUnserializer
) {
Assert::parameter( $cacheExpiry > 0, '$cacheExpiry', 'must be greater than zero' );
parent::__construct( $cacheKey, $revision, $parserOptions, $revisionRenderer );
$this->cacheKey = $cacheKey;
$this->cacheExpiry = $cacheExpiry;
$this->cache = $cache;
$this->jsonUnserializer = $jsonUnserializer;
// TODO: inject logger into all PoolWorkArticleView subclasses via ParserOutputAccess
$this->logger = LoggerFactory::getInstance( 'PoolWorkArticleView' );
$this->cacheable = true;
}
/**
* @param ParserOutput $output
* @param string $cacheTime
*/
protected function saveInCache( ParserOutput $output, string $cacheTime ) {
$json = $this->encodeAsJson( $output );
if ( $json === null ) {
return;
}
$this->cache->set( $this->cacheKey, $json, $this->cacheExpiry );
}
/**
* @return bool
*/
public function getCachedWork() {
$json = $this->cache->get( $this->cacheKey );
if ( $json === false ) {
$this->logger->debug( __METHOD__ . ": output cache miss" );
return false;
} else {
$this->logger->debug( __METHOD__ . ": output cache hit" );
}
$output = $this->restoreFromJson( $json );
// Note: if $output is null, $this->parserOutput remains false, not null.
if ( $output === null ) {
return false;
}
$this->parserOutput = $output;
return true;
}
/**
* @param string $json
*
* @return ParserOutput|null
*/
private function restoreFromJson( string $json ) {
try {
/** @var ParserOutput $obj */
$obj = $this->jsonUnserializer->unserialize( $json, ParserOutput::class );
return $obj;
} catch ( InvalidArgumentException $e ) {
$this->logger->error( "Unable to unserialize JSON", [
'cache_key' => $this->cacheKey,
'message' => $e->getMessage()
] );
return null;
}
}
/**
* @param ParserOutput $output
*
* @return string|null
*/
private function encodeAsJson( ParserOutput $output ) {
$data = $output->jsonSerialize();
$json = FormatJson::encode( $data, false, FormatJson::ALL_OK );
if ( !$json ) {
$this->logger->error( "JSON encoding failed", [
'cache_key' => $this->cacheKey,
'json_error' => json_last_error(),
] );
return null;
}
// Detect if the array contained any properties non-serializable
// to json. We will not be able to deserialize the value correctly
// anyway, so return null. This is done after calling FormatJson::encode
// to avoid walking over circular structures.
$unserializablePath = FormatJson::detectNonSerializableData( $data, true );
if ( $unserializablePath ) {
$this->logger->error( 'Non-serializable {class} property set', [
'class' => get_class( $output ),
'cache_key' => $this->cacheKey,
'path' => $unserializablePath,
] );
return null;
}
return $json;
}
}