StubObject: add magic __get() and __set()
We will soon be converting $wgUser to be a stub object wrapping a User, and we want to be able to access the public properties of that object (doing so will still trigger deprecation warnings, but it won't break). This will also work for non-existent properties that are handled via the __get() and __set() methods of whatever inner object the StubObject is wrapping. Bug: T267861 Change-Id: I4c29c615bcb107d4ef8bf4b8e48db2ecf863e5f7
This commit is contained in:
parent
d28c8785e9
commit
bd73c48744
3 changed files with 263 additions and 1 deletions
|
|
@ -39,14 +39,18 @@ class DeprecatedGlobal extends StubObject {
|
|||
|
||||
// phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
|
||||
public function _newObject() {
|
||||
/* Put the caller offset for wfDeprecated as 6, as
|
||||
/*
|
||||
* Put the caller offset for wfDeprecated as 6, as
|
||||
* that gives the function that uses this object, since:
|
||||
*
|
||||
* 1 = this function ( _newObject )
|
||||
* 2 = StubObject::_unstub
|
||||
* 3 = StubObject::_call
|
||||
* 4 = StubObject::__call
|
||||
* 5 = DeprecatedGlobal::<method of global called>
|
||||
* 6 = Actual function using the global.
|
||||
* (the same applies to _get/__get or _set/__set instead of _call/__call)
|
||||
*
|
||||
* Of course its theoretically possible to have other call
|
||||
* sequences for this method, but that seems to be
|
||||
* rather unlikely.
|
||||
|
|
|
|||
|
|
@ -149,6 +149,50 @@ class StubObject {
|
|||
return $this->_call( $name, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for __get(), similar to _call() above
|
||||
*
|
||||
* @param string $name Name of the property to get
|
||||
* @return mixed
|
||||
*/
|
||||
public function _get( $name ) {
|
||||
$this->_unstub( "__get($name)", 5 );
|
||||
return $GLOBALS[$this->global]->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called by PHP if no property with that name exists in this
|
||||
* object.
|
||||
*
|
||||
* @param string $name Name of the property to get
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get( $name ) {
|
||||
return $this->_get( $name );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for __set(), similar to _call() above
|
||||
*
|
||||
* @param string $name Name of the property to set
|
||||
* @param mixed $value New property value
|
||||
*/
|
||||
public function _set( $name, $value ) {
|
||||
$this->_unstub( "__set($name)", 5 );
|
||||
$GLOBALS[$this->global]->$name = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called by PHP if no property with that name exists in this
|
||||
* object.
|
||||
*
|
||||
* @param string $name Name of the property to set
|
||||
* @param mixed $value New property value
|
||||
*/
|
||||
public function __set( $name, $value ) {
|
||||
$this->_set( $name, $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates a new object of the real class and replace it in
|
||||
* the global variable.
|
||||
|
|
|
|||
214
tests/phpunit/integration/includes/StubObjectTest.php
Normal file
214
tests/phpunit/integration/includes/StubObjectTest.php
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
<?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
|
||||
*/
|
||||
|
||||
/**
|
||||
* Testing the magic for __get(), __set(), and __call() for our
|
||||
* example global, $wgDummy, which would be an instance
|
||||
* of DemoStubbed but is wrapped in a StubObject
|
||||
* @author DannyS712
|
||||
*
|
||||
* @covers \StubObject
|
||||
*/
|
||||
class StubObjectTest extends MediaWikiIntegrationTestCase {
|
||||
|
||||
/** @var int */
|
||||
private $oldErrorLevel;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Make sure deprecation notices are seen
|
||||
$this->oldErrorLevel = error_reporting( -1 );
|
||||
|
||||
global $wgDummy;
|
||||
$wgDummy = new StubObject(
|
||||
'wgDummy',
|
||||
[ __CLASS__, 'factory' ]
|
||||
);
|
||||
}
|
||||
|
||||
protected function tearDown(): void {
|
||||
error_reporting( $this->oldErrorLevel );
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Static factory method for creating the underlying global, which is
|
||||
* a DemoStubbed with the starting value of 5
|
||||
*
|
||||
* @return DemoStubbed
|
||||
*/
|
||||
public static function factory(): DemoStubbed {
|
||||
return new DemoStubbed( 5 );
|
||||
}
|
||||
|
||||
public function testCallMagic() {
|
||||
global $wgDummy;
|
||||
$this->assertInstanceOf(
|
||||
StubObject::class,
|
||||
$wgDummy,
|
||||
'Global starts as stub object'
|
||||
);
|
||||
$this->assertSame(
|
||||
5,
|
||||
$wgDummy->getNum(),
|
||||
'__call() based on id set in ::setUp()'
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
DemoStubbed::class,
|
||||
$wgDummy,
|
||||
'__call() resulted in unstubbing'
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetMagic() {
|
||||
// StubObject::__get() returning DemoStubbed::$num
|
||||
global $wgDummy;
|
||||
$this->assertInstanceOf(
|
||||
StubObject::class,
|
||||
$wgDummy,
|
||||
'Global starts as stub object'
|
||||
);
|
||||
$this->assertSame(
|
||||
5,
|
||||
$wgDummy->num,
|
||||
'__get() based on id set in ::setUp()'
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
DemoStubbed::class,
|
||||
$wgDummy,
|
||||
'__get() resulted in unstubbing'
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetMagic_virtual() {
|
||||
// StubObject::__get() calling DemoStubbed::__get()
|
||||
global $wgDummy;
|
||||
$this->assertInstanceOf(
|
||||
StubObject::class,
|
||||
$wgDummy,
|
||||
'Global starts as stub object'
|
||||
);
|
||||
$this->assertSame(
|
||||
10,
|
||||
$wgDummy->doubleNum,
|
||||
'__get() a virtual property based on id set in ::setUp()'
|
||||
);
|
||||
$this->assertInstanceOf(
|
||||
DemoStubbed::class,
|
||||
$wgDummy,
|
||||
'__get() resulted in unstubbing'
|
||||
);
|
||||
}
|
||||
|
||||
public function testSetMagic() {
|
||||
// StubObject::__set() changing DemoStubbed::$num
|
||||
global $wgDummy;
|
||||
$this->assertInstanceOf(
|
||||
StubObject::class,
|
||||
$wgDummy,
|
||||
'Global starts as stub object'
|
||||
);
|
||||
$wgDummy->num = 100;
|
||||
$this->assertInstanceOf(
|
||||
DemoStubbed::class,
|
||||
$wgDummy,
|
||||
'__set() resulted in unstubbing'
|
||||
);
|
||||
$this->assertSame(
|
||||
100,
|
||||
$wgDummy->num,
|
||||
'__set() changed the value'
|
||||
);
|
||||
}
|
||||
|
||||
public function testSetMagic_virtual() {
|
||||
// StubObject::__set() calling DemoStubbed::__set()
|
||||
global $wgDummy;
|
||||
$this->assertInstanceOf(
|
||||
StubObject::class,
|
||||
$wgDummy,
|
||||
'Global starts as stub object'
|
||||
);
|
||||
$wgDummy->doubleNum = 100;
|
||||
$this->assertInstanceOf(
|
||||
DemoStubbed::class,
|
||||
$wgDummy,
|
||||
'__set() resulted in unstubbing'
|
||||
);
|
||||
$this->assertSame(
|
||||
50,
|
||||
$wgDummy->num,
|
||||
'__set() changed the value'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the object that we are stubbing so we can test the various magic methods
|
||||
*/
|
||||
class DemoStubbed {
|
||||
|
||||
/** @var int */
|
||||
public $num;
|
||||
|
||||
/**
|
||||
* @param int $num
|
||||
*/
|
||||
public function __construct( int $num ) {
|
||||
$this->num = $num;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getNum(): int {
|
||||
return $this->num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic handling for retrieving fake property "doubleNum"
|
||||
*
|
||||
* @param string $field
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get( $field ) {
|
||||
if ( $field === 'doubleNum' ) {
|
||||
return ( 2 * $this->num );
|
||||
}
|
||||
trigger_error( 'Inaccessible property via __get(): ' . $field, E_USER_NOTICE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic handling for setting fake property "doubleNum"
|
||||
*
|
||||
* @param string $field
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __set( $field, $value ) {
|
||||
if ( $field === 'doubleNum' ) {
|
||||
$this->num = (int)( $value / 2 );
|
||||
return;
|
||||
}
|
||||
trigger_error( 'Inaccessible property via __set(): ' . $field, E_USER_NOTICE );
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue