Forward-compat for merging CacheTime and ParserOutput mOptions
CacheTime::mUsedOptions and ParserOutput::mAccessedOptions do exactly the same thing and has to be merged into a single property. This patch adds forward-compatibility and needs to be deployed at least one train before the patch which actually merges the properties. Change-Id: Ic9d71a443994e2545ebf2a826b9155c82961cb88
This commit is contained in:
parent
8eafad0cdd
commit
017cfcf016
21 changed files with 215 additions and 13 deletions
|
|
@ -1886,6 +1886,7 @@ $wgAutoloadLocalClasses = [
|
|||
'Wikimedia\\Rdbms\\TimestampType' => __DIR__ . '/includes/libs/rdbms/database/TimestampType.php',
|
||||
'Wikimedia\\Rdbms\\TinyIntType' => __DIR__ . '/includes/libs/rdbms/database/TinyIntType.php',
|
||||
'Wikimedia\\Rdbms\\TransactionProfiler' => __DIR__ . '/includes/libs/rdbms/TransactionProfiler.php',
|
||||
'Wikimedia\\Reflection\\GhostFieldAccessTrait' => __DIR__ . '/includes/libs/GhostFieldAccessTrait.php',
|
||||
'WikitextContent' => __DIR__ . '/includes/content/WikitextContent.php',
|
||||
'WikitextContentHandler' => __DIR__ . '/includes/content/WikitextContentHandler.php',
|
||||
'WikitextLogFormatter' => __DIR__ . '/includes/logging/WikitextLogFormatter.php',
|
||||
|
|
|
|||
69
includes/libs/GhostFieldAccessTrait.php
Normal file
69
includes/libs/GhostFieldAccessTrait.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?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
|
||||
*/
|
||||
|
||||
namespace Wikimedia\Reflection;
|
||||
|
||||
/**
|
||||
* Trait for accessing "ghost fields".
|
||||
*
|
||||
* Ghost fields are fields that have been created in an object instance
|
||||
* by PHP's unserialize() mechanism, though they no longer exist
|
||||
* in the current version of the corresponding class.
|
||||
*
|
||||
* Accessing non-public ghost fields offers some challenges due to
|
||||
* how they are handled by PHP internally.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/language.types.array.php#language.types.array.casting
|
||||
* @since 1.36
|
||||
*/
|
||||
trait GhostFieldAccessTrait {
|
||||
|
||||
/**
|
||||
* Get the value of the ghost field named $name,
|
||||
* or null if the field does not exist.
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed|null
|
||||
*/
|
||||
private function getGhostFieldValue( string $name ) {
|
||||
if ( isset( $this->$name ) ) {
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
$data = (array)$this;
|
||||
|
||||
// Protected variables have a '*' prepended to the variable name.
|
||||
// These prepended values have null bytes on either side.
|
||||
$protectedName = "\x00*\x00{$name}";
|
||||
if ( isset( $data[$protectedName] ) ) {
|
||||
return $data[$protectedName];
|
||||
}
|
||||
|
||||
// Private variables have the class name prepended to the variable name.
|
||||
// These prepended values have null bytes on either side.
|
||||
$thisClass = get_class( $this );
|
||||
$privateName = "\x00{$thisClass}\x00{$name}";
|
||||
if ( isset( $data[$privateName] ) ) {
|
||||
return $data[$privateName];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
*/
|
||||
|
||||
use MediaWiki\Parser\ParserCacheMetadata;
|
||||
use Wikimedia\Reflection\GhostFieldAccessTrait;
|
||||
|
||||
/**
|
||||
* Parser cache specific expiry check.
|
||||
|
|
@ -29,6 +30,7 @@ use MediaWiki\Parser\ParserCacheMetadata;
|
|||
* @ingroup Parser
|
||||
*/
|
||||
class CacheTime implements ParserCacheMetadata, JsonSerializable {
|
||||
use GhostFieldAccessTrait;
|
||||
|
||||
/**
|
||||
* @var string[] ParserOptions which have been taken into account to produce output.
|
||||
|
|
@ -264,10 +266,22 @@ class CacheTime implements ParserCacheMetadata, JsonSerializable {
|
|||
* @param array $jsonData
|
||||
*/
|
||||
protected function initFromJson( array $jsonData ) {
|
||||
$this->mUsedOptions = $jsonData['UsedOptions'];
|
||||
if ( array_key_exists( 'ParseUsedOptions', $jsonData ) ) {
|
||||
// Forward compatibility
|
||||
$this->mUsedOptions = array_keys( $jsonData['ParseUsedOptions'] ?: [] );
|
||||
} else {
|
||||
$this->mUsedOptions = $jsonData['UsedOptions'];
|
||||
}
|
||||
$this->mCacheExpiry = $jsonData['CacheExpiry'];
|
||||
$this->mCacheTime = $jsonData['CacheTime'];
|
||||
$this->mCacheRevisionId = $jsonData['CacheRevisionId'];
|
||||
$this->mVersion = $jsonData['Version']; // XXX: we can probably remove this
|
||||
}
|
||||
|
||||
public function __wakeup() {
|
||||
$forwardCompatOptions = $this->getGhostFieldValue( 'mParseUsedOptions' );
|
||||
if ( $forwardCompatOptions ) {
|
||||
$this->mUsedOptions = array_keys( $forwardCompatOptions );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\Logger\LoggerFactory;
|
||||
use Wikimedia\Reflection\GhostFieldAccessTrait;
|
||||
|
||||
/**
|
||||
* Output of the PHP parser.
|
||||
|
|
@ -25,6 +26,8 @@ use MediaWiki\Logger\LoggerFactory;
|
|||
*/
|
||||
|
||||
class ParserOutput extends CacheTime {
|
||||
use GhostFieldAccessTrait;
|
||||
|
||||
/**
|
||||
* Feature flags to indicate to extensions that MediaWiki core supports and
|
||||
* uses getText() stateless transforms.
|
||||
|
|
@ -1785,6 +1788,7 @@ class ParserOutput extends CacheTime {
|
|||
*/
|
||||
protected function initFromJson( array $jsonData ) {
|
||||
parent::initFromJson( $jsonData );
|
||||
$this->mUsedOptions = null;
|
||||
|
||||
$this->mText = $jsonData['Text'];
|
||||
$this->mLanguageLinks = $jsonData['LanguageLinks'];
|
||||
|
|
@ -1814,7 +1818,12 @@ class ParserOutput extends CacheTime {
|
|||
$this->mTimestamp = $jsonData['Timestamp'];
|
||||
$this->mEnableOOUI = $jsonData['EnableOOUI'];
|
||||
$this->mIndexPolicy = $jsonData['IndexPolicy'];
|
||||
$this->mAccessedOptions = $jsonData['AccessedOptions'];
|
||||
if ( array_key_exists( 'ParseUsedOptions', $jsonData ) ) {
|
||||
// Forward compatibility
|
||||
$this->mAccessedOptions = $jsonData['ParseUsedOptions'];
|
||||
} else {
|
||||
$this->mAccessedOptions = $jsonData['AccessedOptions'];
|
||||
}
|
||||
$this->mExtensionData = $jsonData['ExtensionData'];
|
||||
$this->mLimitReportData = $jsonData['LimitReportData'];
|
||||
$this->mLimitReportJSData = $jsonData['LimitReportJSData'];
|
||||
|
|
@ -1876,4 +1885,12 @@ class ParserOutput extends CacheTime {
|
|||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
public function __wakeup() {
|
||||
$forwardOptions = $this->getGhostFieldValue( 'mParseUsedOptions' );
|
||||
if ( $forwardOptions ) {
|
||||
$this->mAccessedOptions = $forwardOptions;
|
||||
$this->mUsedOptions = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,10 +140,10 @@ $wgAutoloadClasses += [
|
|||
'ParserIntegrationTest' => "$testDir/phpunit/suites/ParserIntegrationTest.php",
|
||||
'MediaWiki\Tests\Parser\ParserCacheSerializationTestCases' =>
|
||||
"$testDir/phpunit/includes/parser/ParserCacheSerializationTestCases.php",
|
||||
'MediaWiki\Tests\Parser\SerializationTestTrait' =>
|
||||
"$testDir/phpunit/includes/parser/SerializationTestTrait.php",
|
||||
'MediaWiki\Tests\Parser\SerializationTestUtils' =>
|
||||
"$testDir/phpunit/includes/parser/SerializationTestUtils.php",
|
||||
'Wikimedia\Tests\SerializationTestTrait' =>
|
||||
"$testDir/phpunit/includes/libs/serialization/SerializationTestTrait.php",
|
||||
'Wikimedia\Tests\SerializationTestUtils' =>
|
||||
"$testDir/phpunit/includes/libs/serialization/SerializationTestUtils.php",
|
||||
|
||||
# tests/phpunit/includes/resourceloader
|
||||
'ResourceLoaderImageModuleTest' =>
|
||||
|
|
@ -223,6 +223,7 @@ $wgAutoloadClasses += [
|
|||
|
||||
# tests/phpunit/unit/includes
|
||||
'BadFileLookupTest' => "$testDir/phpunit/unit/includes/BadFileLookupTest.php",
|
||||
'Wikimedia\\Reflection\\GhostFieldTestClass' => "$testDir/phpunit/mocks/GhostFieldTestClass.php",
|
||||
|
||||
# tests/phpunit/unit/includes/editpage/Constraint and tests/phpunit/integration/includes/editpage/Constraint
|
||||
'EditConstraintTestTrait' => "$testDir/phpunit/unit/includes/editpage/Constraint/EditConstraintTestTrait.php",
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
{"_type_":"CacheTime","ParseUsedOptions":{"optA":true,"optX":true},"CacheExpiry":null,"CacheTime":"","CacheRevisionId":null,"Version":"1.6.4"}
|
||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
{"_type_":"ParserOutput","Text":"","LanguageLinks":[],"Categories":[],"Indicators":[],"TitleText":"","Links":[],"LinksSpecial":[],"Templates":[],"TemplateIds":[],"Images":[],"FileSearchOptions":[],"ExternalLinks":[],"InterwikiLinks":[],"NewSection":false,"HideNewSection":false,"NoGallery":false,"HeadItems":[],"Modules":[],"ModuleStyles":[],"JsConfigVars":[],"OutputHooks":[],"Warnings":[],"Sections":[],"Properties":[],"TOCHTML":"","Timestamp":null,"EnableOOUI":false,"IndexPolicy":"","ExtensionData":[],"LimitReportData":[],"LimitReportJSData":[],"ParseStartTime":[],"PreventClickjacking":false,"ExtraScriptSrcs":[],"ExtraDefaultSrcs":[],"ExtraStyleSrcs":[],"Flags":[],"SpeculativeRevId":null,"SpeculativePageIdUsed":null,"RevisionTimestampUsed":null,"RevisionUsedSha1Base36":null,"WrapperDivClasses":[],"ParseUsedOptions":null,"CacheExpiry":null,"CacheTime":"","CacheRevisionId":null,"Version":"1.6.4"}
|
||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
{"_type_":"ParserOutput","Text":"Dummy","LanguageLinks":[],"Categories":[],"Indicators":[],"TitleText":"","Links":[],"LinksSpecial":[],"Templates":[],"TemplateIds":[],"Images":[],"FileSearchOptions":[],"ExternalLinks":[],"InterwikiLinks":[],"NewSection":false,"HideNewSection":false,"NoGallery":false,"HeadItems":[],"Modules":[],"ModuleStyles":[],"JsConfigVars":[],"OutputHooks":[],"Warnings":[],"Sections":[],"Properties":[],"TOCHTML":"","Timestamp":null,"EnableOOUI":false,"IndexPolicy":"","ExtensionData":[],"LimitReportData":[],"LimitReportJSData":[],"ParseStartTime":[],"PreventClickjacking":false,"ExtraScriptSrcs":[],"ExtraDefaultSrcs":[],"ExtraStyleSrcs":[],"Flags":[],"SpeculativeRevId":null,"SpeculativePageIdUsed":null,"RevisionTimestampUsed":null,"RevisionUsedSha1Base36":null,"WrapperDivClasses":[],"ParseUsedOptions":{"optA":true,"optX":true},"CacheExpiry":null,"CacheTime":"","CacheRevisionId":null,"Version":"1.6.4"}
|
||||
Binary file not shown.
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace MediaWiki\Tests\Parser;
|
||||
namespace Wikimedia\Tests;
|
||||
|
||||
use Generator;
|
||||
use ReflectionClass;
|
||||
|
|
@ -19,9 +19,8 @@
|
|||
* @ingroup Cache Parser
|
||||
*/
|
||||
|
||||
namespace MediaWiki\Tests\Parser;
|
||||
namespace Wikimedia\Tests;
|
||||
|
||||
use CacheTime;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
|
@ -167,7 +166,7 @@ class SerializationTestUtils {
|
|||
*/
|
||||
public function getSerializedInstances(): array {
|
||||
$instances = $this->getTestInstances();
|
||||
return array_map( function ( CacheTime $object ) {
|
||||
return array_map( function ( $object ) {
|
||||
return call_user_func( $this->serializer, $object );
|
||||
}, $instances );
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ namespace MediaWiki\Tests\Parser;
|
|||
use CacheTime;
|
||||
use MediaWikiIntegrationTestCase;
|
||||
use MWTimestamp;
|
||||
use Wikimedia\Tests\SerializationTestTrait;
|
||||
|
||||
/**
|
||||
* @covers CacheTime
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use MediaWikiIntegrationTestCase;
|
|||
use MWTimestamp;
|
||||
use ParserOutput;
|
||||
use Title;
|
||||
use Wikimedia\Tests\SerializationTestUtils;
|
||||
|
||||
/**
|
||||
* A collection of serialization test cases for parser cache.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\Tests\Parser\ParserCacheSerializationTestCases;
|
||||
use MediaWiki\Tests\Parser\SerializationTestTrait;
|
||||
use Wikimedia\TestingAccessWrapper;
|
||||
use Wikimedia\Tests\SerializationTestTrait;
|
||||
|
||||
/**
|
||||
* @covers ParserOutput
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use ParserOutput;
|
|||
|
||||
require_once __DIR__ . '/../../../../maintenance/Maintenance.php';
|
||||
require __DIR__ . '/ParserCacheSerializationTestCases.php';
|
||||
require __DIR__ . '/SerializationTestUtils.php';
|
||||
require __DIR__ . '/../libs/serialization/SerializationTestUtils.php';
|
||||
|
||||
class ValidateParserCacheSerializationTestData extends Maintenance {
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ class ValidateParserCacheSerializationTestData extends Maintenance {
|
|||
public function validateSerialization( string $className, array $testInstances ) {
|
||||
$supportedFormats = ParserCacheSerializationTestCases::getSupportedSerializationFormats( $className );
|
||||
foreach ( $supportedFormats as $serializationFormat ) {
|
||||
$serializationUtils = new SerializationTestUtils(
|
||||
$serializationUtils = new \Wikimedia\Tests\SerializationTestUtils(
|
||||
$this->getArg( 1 ) ?: __DIR__ . '/../../data/ParserCache',
|
||||
$testInstances,
|
||||
$serializationFormat['ext'],
|
||||
|
|
|
|||
25
tests/phpunit/mocks/GhostFieldTestClass.php
Normal file
25
tests/phpunit/mocks/GhostFieldTestClass.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Wikimedia\Reflection;
|
||||
|
||||
/**
|
||||
* This class used to contain a $privateField, $protectedField and $publicField.
|
||||
* This is used to test that unserialized instances still have the values of
|
||||
* these ghost fields and the values can be accessed with GhostFieldAccessTrait.
|
||||
* @package Wikimedia\Reflection
|
||||
*/
|
||||
class GhostFieldTestClass {
|
||||
use GhostFieldAccessTrait;
|
||||
|
||||
public function getPrivateField() {
|
||||
return $this->getGhostFieldValue( 'privateField' );
|
||||
}
|
||||
|
||||
public function getProtectedField() {
|
||||
return $this->getGhostFieldValue( 'protectedField' );
|
||||
}
|
||||
|
||||
public function getPublicField() {
|
||||
return $this->getGhostFieldValue( 'publicField' );
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace Wikimedia\Tests\Reflection;
|
||||
|
||||
use MediaWikiUnitTestCase;
|
||||
use Wikimedia\Reflection\GhostFieldTestClass;
|
||||
use Wikimedia\Tests\SerializationTestUtils;
|
||||
|
||||
/**
|
||||
* @covers \Wikimedia\Reflection\GhostFieldAccessTrait
|
||||
* @package Wikimedia\Tests\Reflection
|
||||
*/
|
||||
class GhostFieldAccessTraitTest extends MediaWikiUnitTestCase {
|
||||
|
||||
private $serializationTestUtils;
|
||||
|
||||
protected function setUp() : void {
|
||||
parent::setUp();
|
||||
// Not using the trait since we only need deserialization tests.
|
||||
$this->serializationTestUtils = new SerializationTestUtils(
|
||||
__DIR__ . '/../../data/GhostFieldAccess',
|
||||
[],
|
||||
'serialized',
|
||||
'serialize',
|
||||
'unserialize'
|
||||
);
|
||||
}
|
||||
|
||||
private function provideUnserializedInstances( string $testCaseName ) {
|
||||
$serializationTestUtils = new SerializationTestUtils(
|
||||
__DIR__ . '/../../data/GhostFieldAccess',
|
||||
[],
|
||||
'serialized',
|
||||
'serialize',
|
||||
'unserialize'
|
||||
);
|
||||
$instances = $serializationTestUtils
|
||||
->getDeserializedInstancesForTestCase( 'GhostFieldTestClass', $testCaseName );
|
||||
foreach ( $instances as $instance ) {
|
||||
yield "{$instance->version}" => [ $instance->object ];
|
||||
}
|
||||
}
|
||||
|
||||
public function provideUnserializedInstancesWithValues() {
|
||||
return $this->provideUnserializedInstances( 'withValues' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideUnserializedInstancesWithValues
|
||||
* @param GhostFieldTestClass $instance
|
||||
*/
|
||||
public function testUnserializedWithValues( GhostFieldTestClass $instance ) {
|
||||
$this->assertSame( 'private_value', $instance->getPrivateField() );
|
||||
$this->assertSame( 'protected_value', $instance->getProtectedField() );
|
||||
$this->assertSame( 'public_value', $instance->getPublicField() );
|
||||
}
|
||||
|
||||
public function provideUnserializedInstancesWithNulls() {
|
||||
return $this->provideUnserializedInstances( 'withNulls' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideUnserializedInstancesWithNulls
|
||||
* @param GhostFieldTestClass $instance
|
||||
*/
|
||||
public function testUnserializedWithNulls( GhostFieldTestClass $instance ) {
|
||||
$this->assertNull( $instance->getPrivateField() );
|
||||
$this->assertNull( $instance->getProtectedField() );
|
||||
$this->assertNull( $instance->getPublicField() );
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue