Introduce DeprecatablePropertyArray and use it for PageUpdater

Bug: T250638
Change-Id: I53e39be59228ac5a57f34d51d733d1647331889c
This commit is contained in:
Petr Pchelko 2020-06-03 09:37:46 -07:00
parent 4c7fbea780
commit 2704be7df8
4 changed files with 210 additions and 2 deletions

View file

@ -859,6 +859,7 @@ $wgAutoloadLocalClasses = [
'MediaWikiSite' => __DIR__ . '/includes/site/MediaWikiSite.php',
'MediaWikiTitleCodec' => __DIR__ . '/includes/title/MediaWikiTitleCodec.php',
'MediaWikiVersionFetcher' => __DIR__ . '/includes/MediaWikiVersionFetcher.php',
'MediaWiki\\Debug\\DeprecatablePropertyArray' => __DIR__ . '/includes/debug/DeprecatablePropertyArray.php',
'MediaWiki\\FileBackend\\FSFile\\TempFSFileFactory' => __DIR__ . '/includes/libs/filebackend/fsfile/TempFSFileFactory.php',
'MediaWiki\\Hook\\AbortEmailNotificationHook' => __DIR__ . '/includes/changes/Hook/AbortEmailNotificationHook.php',
'MediaWiki\\Hook\\AbortTalkPageEmailNotificationHook' => __DIR__ . '/includes/mail/Hook/AbortTalkPageEmailNotificationHook.php',

View file

@ -34,6 +34,7 @@ use InvalidArgumentException;
use LogicException;
use ManualLogEntry;
use MediaWiki\Content\IContentHandlerFactory;
use MediaWiki\Debug\DeprecatablePropertyArray;
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\HookContainer\HookRunner;
use MediaWiki\Linker\LinkTarget;
@ -983,7 +984,13 @@ class PageUpdater {
$wikiPage = $this->getWikiPage(); // TODO: use for legacy hooks only!
// Update article, but only if changed.
$status = Status::newGood( [ 'new' => false, 'revision' => null, 'revision-record' => null ] );
$status = Status::newGood(
new DeprecatablePropertyArray(
[ 'new' => false, 'revision' => null, 'revision-record' => null ],
[], // TODO: [ 'revision' => '1.35' ],
__METHOD__ . ' status'
)
);
$oldRev = $this->grabParentRevision();
$oldid = $oldRev ? $oldRev->getId() : 0;
@ -1139,7 +1146,13 @@ class PageUpdater {
throw new PageUpdateException( 'Must provide a main slot when creating a page!' );
}
$status = Status::newGood( [ 'new' => true, 'revision' => null, 'revision-record' => null ] );
$status = Status::newGood(
new DeprecatablePropertyArray(
[ 'new' => true, 'revision' => null, 'revision-record' => null ],
[], // TODO: [ 'revision' => '1.35' ],
__METHOD__ . ' status'
)
);
$newRevisionRecord = $this->makeNewRevision(
$summary,

View file

@ -0,0 +1,94 @@
<?php
namespace MediaWiki\Debug;
use ArrayAccess;
/**
* ArrayAccess implementation that supports deprecating access to certain properties.
* It behaves mostly as a normal array, however in order to avoid instantiating
* deprecated properties by default, a callable initializer can be set to the property.
* It will be executed upon 'get'.
* @note setting properties does not emit deprecation warnings.
* @newable
* @since 1.35
*/
class DeprecatablePropertyArray implements ArrayAccess {
/** @var array */
private $container;
/** @var array Map of deprecated property names to deprecation versions */
private $deprecatedProperties;
/** @var string */
private $name;
/** @var string|null */
private $component;
/**
* @param array $initializer Initial value of the array.
* @param array $deprecatedProperties Map of deprecated property names to versions.
* @param string $name Descriptive identifier for the array
* @param string|null $component Component to which array belongs.
* If not provided, assumed to be MW Core
*/
public function __construct(
array $initializer,
array $deprecatedProperties,
string $name,
string $component = null
) {
$this->container = $initializer;
$this->deprecatedProperties = $deprecatedProperties;
$this->name = $name;
$this->component = $component;
}
public function offsetExists( $offset ) {
$this->checkDeprecatedAccess( $offset, 'exists' );
return isset( $this->container[$offset] );
}
public function offsetGet( $offset ) {
if ( $this->checkDeprecatedAccess( $offset, 'get' ) ) {
if ( is_callable( $this->container[$offset] ) ) {
$this->container[$offset] = call_user_func( $this->container[$offset] );
}
}
return $this->container[$offset] ?? null;
}
public function offsetSet( $offset, $value ) {
if ( $offset === null ) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetUnset( $offset ) {
$this->checkDeprecatedAccess( $offset, 'unset' );
unset( $this->container[$offset] );
}
/**
* @param string|int $offset
* @param string $fname
* @return bool
*/
private function checkDeprecatedAccess( $offset, string $fname ) : bool {
if ( array_key_exists( $offset, $this->deprecatedProperties ) ) {
$deprecatedVersion = $this->deprecatedProperties[$offset];
wfDeprecated(
"{$this->name} {$fname} '{$offset}'",
$deprecatedVersion,
$this->component ?? false,
3
);
return true;
}
return false;
}
}

View file

@ -0,0 +1,100 @@
<?php
use MediaWiki\Debug\DeprecatablePropertyArray;
use Wikimedia\TestingAccessWrapper;
/**
* @covers \MediaWiki\Debug\DeprecatablePropertyArray
*/
class DeprecatablePropertyArrayTest extends MediaWikiUnitTestCase {
private const PROP_NAME = 'test_property';
/**
* @dataProvider provideDeprecationWarning
* @param callable $callback
*/
public function testDeprecationWarning( callable $callback, string $message ) {
$GLOBALS['wgDevelopmentWarnings'] = false;
$this->assertDeprecationWarningIssued( $callback, $message );
}
public function provideDeprecationWarning() {
$propName = self::PROP_NAME;
$array = new DeprecatablePropertyArray(
[
self::PROP_NAME => 'test_value',
'callback' => function () {
return 'callback_test_value';
},
],
[
self::PROP_NAME => 'DEPRECATED_VERSION',
'callback' => 'DEPRECATED_VERSION',
],
'TEST'
);
yield 'get' => [
function () use ( $array ) {
$this->assertSame( 'test_value', $array[ self::PROP_NAME ] );
},
"TEST get '{$propName}'"
];
yield 'get, callback' => [
function () use ( $array ) {
$this->assertSame( 'callback_test_value', $array[ 'callback' ] );
},
"TEST get 'callback'"
];
yield 'exists' => [
function () use ( $array ) {
$this->assertTrue( isset( $array[ self::PROP_NAME ] ) );
},
"TEST exists '{$propName}'"
];
yield 'unset' => [
function () use ( $array ) {
unset( $array[ self::PROP_NAME ] );
},
"TEST unset '{$propName}'"
];
}
public function testNonDeprecated() {
$array = new DeprecatablePropertyArray( [], [], __METHOD__ );
$this->assertFalse( isset( $array[self::PROP_NAME] ) );
$array[self::PROP_NAME] = 'test_value';
$this->assertTrue( isset( $array[self::PROP_NAME] ) );
$this->assertSame( 'test_value', $array[self::PROP_NAME] );
unset( $array[self::PROP_NAME] );
$this->assertFalse( isset( $array[self::PROP_NAME] ) );
}
public function testNonDeprecatedNumerical() {
$array = new DeprecatablePropertyArray( [], [], __METHOD__ );
$this->assertFalse( isset( $array[0] ) );
$array[] = 'test_value';
$this->assertTrue( isset( $array[0] ) );
$this->assertSame( 'test_value', $array[0] );
unset( $array[0] );
$this->assertFalse( isset( $array[0] ) );
}
/**
* Assert that $expectedMessage deprecation warning was emitted while
* executing the $callback.
* @param callable $callback
* @param string $expectedMessage
*/
protected function assertDeprecationWarningIssued(
callable $callback,
string $expectedMessage
) {
MWDebug::clearLog();
$callback();
$wrapper = TestingAccessWrapper::newFromClass( MWDebug::class );
$this->assertCount( 1, $wrapper->deprecationWarnings );
$this->assertArrayHasKey( $expectedMessage, $wrapper->deprecationWarnings );
}
}