6 use InvalidArgumentException;
8 use ReflectionException;
10 use ReflectionProperty;
13 * Circumvent access restrictions on object internals
15 * This can be helpful for writing tests that can probe object internals,
16 * without having to modify the class under test to accomodate.
18 * Wrap an object with private methods as follows:
19 * $title = TestingAccessWrapper::newFromObject( Title::newFromDBkey( $key ) );
21 * You can access private and protected instance methods and variables:
22 * $formatter = $title->getTitleFormatter();
25 class TestingAccessWrapper {
26 /** @var mixed The object, or the class name for static-only access */
30 * Return a proxy object which can be used the same way as the original,
31 * except that access restrictions can be ignored (protected and private methods and properties
32 * are available for any caller).
33 * @param object $object
34 * @return TestingAccessWrapper
35 * @throws InvalidArgumentException
37 public static function newFromObject( $object ) {
38 if ( !is_object( $object ) ) {
39 throw new InvalidArgumentException( __METHOD__ . ' must be called with an object' );
41 $wrapper = new TestingAccessWrapper();
42 $wrapper->object = $object;
47 * Allow access to non-public static methods and properties of the class.
48 * Returns an object whose methods/properties will correspond to the
49 * static methods/properties of the given class.
50 * @param string $className
51 * @return TestingAccessWrapper
52 * @throws InvalidArgumentException
54 public static function newFromClass( $className ) {
55 if ( !is_string( $className ) ) {
56 throw new InvalidArgumentException( __METHOD__ . ' must be called with a class name' );
58 $wrapper = new TestingAccessWrapper();
59 $wrapper->object = $className;
63 public function __call( $method, $args ) {
64 $methodReflection = $this->getMethod( $method );
66 if ( $this->isStatic() && !$methodReflection->isStatic() ) {
67 throw new DomainException( __METHOD__
68 . ': Cannot call non-static method when wrapping static class' );
71 return $methodReflection->invokeArgs( $methodReflection->isStatic() ? null : $this->object,
75 public function __set( $name, $value ) {
76 $propertyReflection = $this->getProperty( $name );
78 if ( $this->isStatic() && !$propertyReflection->isStatic() ) {
79 throw new DomainException( __METHOD__
80 . ': Cannot set non-static property when wrapping static class' );
83 $propertyReflection->setValue( $this->object, $value );
86 public function __get( $name ) {
87 $propertyReflection = $this->getProperty( $name );
89 if ( $this->isStatic() && !$propertyReflection->isStatic() ) {
90 throw new DomainException( __METHOD__
91 . ': Cannot get non-static property when wrapping static class' );
94 return $propertyReflection->getValue( $this->object );
98 * Tells whether this object was created for an object or a class.
101 private function isStatic() {
102 return is_string( $this->object );
106 * Return a method and make it accessible.
107 * @param string $name
108 * @return ReflectionMethod
110 private function getMethod( $name ) {
111 $classReflection = new ReflectionClass( $this->object );
112 $methodReflection = $classReflection->getMethod( $name );
113 $methodReflection->setAccessible( true );
114 return $methodReflection;
118 * Return a property and make it accessible.
120 * ReflectionClass::getProperty() fails if the private property is defined
121 * in a parent class. This works more like ReflectionClass::getMethod().
123 * @param string $name
124 * @return ReflectionProperty
125 * @throws ReflectionException
127 private function getProperty( $name ) {
128 $classReflection = new ReflectionClass( $this->object );
130 $propertyReflection = $classReflection->getProperty( $name );
131 } catch ( ReflectionException $ex ) {
133 $classReflection = $classReflection->getParentClass();
134 if ( !$classReflection ) {
138 $propertyReflection = $classReflection->getProperty( $name );
139 } catch ( ReflectionException $ex2 ) {
142 if ( $propertyReflection->isPrivate() ) {
145 // @codeCoverageIgnoreStart
147 // @codeCoverageIgnoreEnd
151 $propertyReflection->setAccessible( true );
152 return $propertyReflection;