<?php
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 TestingAccessWrapper
* @throws InvalidArgumentException
*/
public static function newFromObject( $object ) {
if ( !is_object( $object ) ) {
throw new InvalidArgumentException( __METHOD__ . ' must be called with an object' );
}
$wrapper = new TestingAccessWrapper();
$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 string $className
* @return TestingAccessWrapper
* @throws InvalidArgumentException
*/
public static function newFromClass( $className ) {
if ( !is_string( $className ) ) {
throw new InvalidArgumentException( __METHOD__ . ' must be called with a class name' );
}
$wrapper = new TestingAccessWrapper();
$wrapper->object = $className;
return $wrapper;
}
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 );
}
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' );
}
$propertyReflection->setValue( $this->object, $value );
}
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' );
}
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
*/
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;
}
}
class A {
private static function func( &$ref ) {
$ref = 42;
}
}
$a = TestingAccessWrapper::newFromClass( A::class );
$b = 0;
$a->func( &$b );
echo $b;
Parse error: syntax error, unexpected '&' in /in/mqEne on line 145
Process exited with code 255.
Output for 5.5.0 - 5.5.38, 5.6.0 - 5.6.40
Fatal error: Call-time pass-by-reference has been removed in /in/mqEne on line 145
Process exited with code 255.
Output for 5.4.0 - 5.4.45
Parse error: syntax error, unexpected 'class' (T_CLASS), expecting identifier (T_STRING) or variable (T_VARIABLE) or '{' or '$' in /in/mqEne on line 142
Process exited with code 255.
Parse error: syntax error, unexpected T_CLASS, expecting T_STRING or T_VARIABLE or '$' in /in/mqEne on line 142
Process exited with code 255.
Output for 5.0.0 - 5.0.5
Parse error: parse error, unexpected T_CLASS, expecting T_STRING or T_VARIABLE or '$' in /in/mqEne on line 142
Process exited with code 255.
Output for 4.4.2 - 4.4.9
Parse error: syntax error, unexpected T_STRING, expecting T_OLD_FUNCTION or T_FUNCTION or T_VAR or '}' in /in/mqEne on line 5
Process exited with code 255.
Parse error: parse error, unexpected T_STRING, expecting T_OLD_FUNCTION or T_FUNCTION or T_VAR or '}' in /in/mqEne on line 5
Process exited with code 255.
Output for 4.3.2 - 4.3.4
Parse error: parse error, expecting `T_OLD_FUNCTION' or `T_FUNCTION' or `T_VAR' or `'}'' in /in/mqEne on line 5
Process exited with code 255.