Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F2752346
TestingAccessWrapper.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
6 KB
Referenced Files
None
Subscribers
None
TestingAccessWrapper.php
View Options
<?php
namespace
Wikimedia
;
use
DomainException
;
use
InvalidArgumentException
;
use
ReflectionClass
;
use
ReflectionException
;
use
ReflectionMethod
;
use
ReflectionProperty
;
/**
* Circumvent access restrictions on object internals
*
* This can be helpful for writing tests that can probe object internals,
* without having to modify the class under test to accommodate.
*
* Wrap an object with private methods as follows:
* $title = TestingAccessWrapper::newFromObject( Title::newFromDBkey( $key ) );
*
* You can access private and protected instance methods and variables:
* $formatter = $title->getTitleFormatter();
*
* You can access private and protected constants:
* $value = TestingAccessWrapper::constant( Foo::class, 'FOO_CONSTANT' );
*
*/
class
TestingAccessWrapper
{
/** @var mixed The object, or the class name for static-only access */
public
$object
;
/**
* Return a proxy object which can be used the same way as the original,
* except that access restrictions can be ignored (protected and private methods and properties
* are available for any caller).
* @param object $object
* @return self
* @throws InvalidArgumentException
*/
public
static
function
newFromObject
(
$object
)
{
if
(
!
is_object
(
$object
)
)
{
throw
new
InvalidArgumentException
(
__METHOD__
.
' must be called with an object'
);
}
$wrapper
=
new
self
();
$wrapper
->
object
=
$object
;
return
$wrapper
;
}
/**
* Allow access to non-public static methods and properties of the class.
* Returns an object whose methods/properties will correspond to the
* static methods/properties of the given class.
* @param class-string $className
* @return self
* @throws InvalidArgumentException
*/
public
static
function
newFromClass
(
$className
)
{
if
(
!
is_string
(
$className
)
)
{
throw
new
InvalidArgumentException
(
__METHOD__
.
' must be called with a class name'
);
}
$wrapper
=
new
self
();
$wrapper
->
object
=
$className
;
return
$wrapper
;
}
/**
* Allow access to non-public constants of the class.
* @param class-string $className
* @param string $constantName
* @return mixed
*/
public
static
function
constant
(
$className
,
$constantName
)
{
$classReflection
=
new
ReflectionClass
(
$className
);
// getConstant() returns `false` if the constant is defined in
// a parent class; this works more like ReflectionClass::getMethod()
while
(
!
$classReflection
->
hasConstant
(
$constantName
)
)
{
$classReflection
=
$classReflection
->
getParentClass
();
if
(
!
$classReflection
)
{
throw
new
ReflectionException
(
'constant not present'
);
}
}
return
$classReflection
->
getConstant
(
$constantName
);
}
/**
* Allow constructing a class with a non-public constructor.
* @param class-string<T> $className
* @param mixed ...$args
* @return T
* @phan-template T
*/
public
static
function
construct
(
string
$className
,
...
$args
)
{
$classReflection
=
new
ReflectionClass
(
$className
);
$constructor
=
$classReflection
->
getConstructor
();
$constructor
->
setAccessible
(
true
);
$object
=
$classReflection
->
newInstanceWithoutConstructor
();
$constructor
->
invokeArgs
(
$object
,
$args
);
return
$object
;
}
/**
* @param string $method
* @param array $args
* @return mixed
*/
public
function
__call
(
$method
,
$args
)
{
$methodReflection
=
$this
->
getMethod
(
$method
);
if
(
$this
->
isStatic
()
&&
!
$methodReflection
->
isStatic
()
)
{
throw
new
DomainException
(
__METHOD__
.
': Cannot call non-static method when wrapping static class'
);
}
return
$methodReflection
->
invokeArgs
(
$methodReflection
->
isStatic
()
?
null
:
$this
->
object
,
$args
);
}
/**
* @param string $name
* @param mixed $value
*/
public
function
__set
(
$name
,
$value
)
{
$propertyReflection
=
$this
->
getProperty
(
$name
);
if
(
$this
->
isStatic
()
&&
!
$propertyReflection
->
isStatic
()
)
{
throw
new
DomainException
(
__METHOD__
.
': Cannot set non-static property when wrapping static class'
);
}
if
(
$this
->
isStatic
()
)
{
$class
=
new
ReflectionClass
(
$this
->
object
);
$class
->
setStaticPropertyValue
(
$name
,
$value
);
}
else
{
$propertyReflection
->
setValue
(
$this
->
object
,
$value
);
}
}
/**
* @param string $name Field name
* @return mixed
*/
public
function
__get
(
$name
)
{
$propertyReflection
=
$this
->
getProperty
(
$name
);
if
(
$this
->
isStatic
()
&&
!
$propertyReflection
->
isStatic
()
)
{
throw
new
DomainException
(
__METHOD__
.
': Cannot get non-static property when wrapping static class'
);
}
if
(
$propertyReflection
->
isStatic
()
)
{
// https://bugs.php.net/bug.php?id=69804 - can't use getStaticPropertyValue() on
// non-public properties
$class
=
new
ReflectionClass
(
$this
->
object
);
$props
=
$class
->
getStaticProperties
();
// Can't use isset() as it returns false for null values
if
(
!
array_key_exists
(
$name
,
$props
)
)
{
throw
new
DomainException
(
__METHOD__
.
": class {$class->name} "
.
"doesn't have static property '{$name}'"
);
}
return
$props
[
$name
];
}
return
$propertyReflection
->
getValue
(
$this
->
object
);
}
/**
* Tells whether this object was created for an object or a class.
* @return bool
*/
private
function
isStatic
()
{
return
is_string
(
$this
->
object
);
}
/**
* Return a method and make it accessible.
* @param string $name
* @return ReflectionMethod
* @throws ReflectionException
*/
private
function
getMethod
(
$name
)
{
$classReflection
=
new
ReflectionClass
(
$this
->
object
);
$methodReflection
=
$classReflection
->
getMethod
(
$name
);
$methodReflection
->
setAccessible
(
true
);
return
$methodReflection
;
}
/**
* Return a property and make it accessible.
*
* ReflectionClass::getProperty() fails if the private property is defined
* in a parent class. This works more like ReflectionClass::getMethod().
*
* @param string $name
* @return ReflectionProperty
* @throws ReflectionException
*/
private
function
getProperty
(
$name
)
{
$classReflection
=
new
ReflectionClass
(
$this
->
object
);
try
{
$propertyReflection
=
$classReflection
->
getProperty
(
$name
);
}
catch
(
ReflectionException
$ex
)
{
while
(
true
)
{
$classReflection
=
$classReflection
->
getParentClass
();
if
(
!
$classReflection
)
{
throw
$ex
;
}
try
{
$propertyReflection
=
$classReflection
->
getProperty
(
$name
);
}
catch
(
ReflectionException
$ex2
)
{
continue
;
}
if
(
$propertyReflection
->
isPrivate
()
)
{
break
;
}
else
{
// @codeCoverageIgnoreStart
throw
$ex
;
// @codeCoverageIgnoreEnd
}
}
}
$propertyReflection
->
setAccessible
(
true
);
return
$propertyReflection
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Fri, Jul 3, 19:54 (1 d, 3 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
0a/94/a5f0d9d1522e33efe27ea318341b
Default Alt Text
TestingAccessWrapper.php (6 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment