Buscar..


Acceso a variables de miembros privados y protegidos.

La reflexión se utiliza a menudo como parte de las pruebas de software, como para la creación / creación de instancias de objetos simulados en tiempo de ejecución. También es ideal para inspeccionar el estado de un objeto en un momento dado. Este es un ejemplo del uso de Reflexión en una prueba unitaria para verificar que un miembro de clase protegido contenga el valor esperado.

A continuación se muestra una clase muy básica para un coche. Tiene una variable miembro protegida que contendrá el valor que representa el color del automóvil. Debido a que la variable miembro está protegida, no podemos acceder a ella directamente y debemos usar un método de obtención y configuración para recuperar y establecer su valor respectivamente.

class Car
{
    protected $color
    
    public function setColor($color)
    {
        $this->color = $color;
    }
    
    public function getColor($color)
    {
        return $this->color;
    }
}

Para probar esto, muchos desarrolladores crearán un objeto Car, establecerán el color del auto usando Car::setColor() , recuperarán el color usando Car::getColor() y compararán ese valor con el color que establecieron:

/**
 * @test
 * @covers     \Car::setColor
 */
public function testSetColor()
{
    $color = 'Red';

    $car = new \Car();
    $car->setColor($color);
    $getColor = $car->getColor();
        
    $this->assertEquals($color, $reflectionColor);
}

En la superficie esto parece estar bien. Después de todo, todo lo que hace Car::getColor() es devolver el valor de la variable miembro protegida Car::$color . Pero esta prueba es defectuosa de dos maneras:

  1. Ejercita Car::getColor() que está fuera del alcance de esta prueba.
  2. Depende de Car::getColor() que puede tener un error que puede hacer que la prueba tenga un falso positivo o negativo

Veamos por qué no deberíamos usar Car::getColor() en nuestra prueba de unidad y deberíamos usar Reflection en su lugar. Digamos que a un desarrollador se le asigna una tarea para agregar "Metallic" a cada color de automóvil. Así que intentan modificar el Car::getColor() para anteponer "Metallic" al color del auto:

class Car
{
    protected $color
    
    public function setColor($color)
    {
        $this->color = $color;
    }
    
    public function getColor($color)
    {
        return "Metallic "; $this->color;
    }
}

¿Ves el error? El desarrollador usó un punto y coma en lugar del operador de concatenación en un intento de agregar "Metallic" al color del auto. Como resultado, siempre que se Car::getColor() , se devolverá "Metallic" independientemente del color real del auto. Como resultado, nuestra prueba de la unidad Car::setColor() fallará a pesar de que Car::setColor() funciona perfectamente bien y no se vio afectado por este cambio .

Entonces, ¿cómo verificamos que Car::$color contiene el valor que estamos configurando a través de Car::setColor() ? Podemos usar la Refelección para inspeccionar la variable miembro protegida directamente. Entonces, ¿cómo hacemos eso ? Podemos usar la Refelección para hacer que la variable miembro protegida sea accesible a nuestro código para que pueda recuperar el valor.

Veamos primero el código y luego lo desglosamos:

/**
 * @test
 * @covers     \Car::setColor
 */
public function testSetColor()
{
    $color = 'Red';

    $car = new \Car();
    $car->setColor($color);
    
    $reflectionOfCar = new \ReflectionObject($car);
    $protectedColor = $reflectionOfForm->getProperty('color');
    $protectedColor->setAccessible(true);
    $reflectionColor = $protectedColor->getValue($car);
    
    $this->assertEquals($color, $reflectionColor);
}

Aquí es cómo estamos utilizando Reflection para obtener el valor de Car::$color en el código anterior:

  1. Creamos un nuevo ReflectionObject que representa nuestro objeto Car.
  2. Obtenemos una propiedad ReflectionProperty for Car::$color (esto "representa" la variable Car::$color )
  3. Hacemos Car::$color accesible
  4. Obtenemos el valor de Car::$color

Como puede ver al usar Reflection, podemos obtener el valor de Car::$color sin tener que llamar a Car::getColor() o cualquier otra función de acceso que pueda causar resultados de prueba no válidos. Ahora nuestra prueba de unidad para Car::setColor() es segura y precisa.

Detección de características de clases u objetos.

La detección de características de clases se puede realizar en parte con las funciones property_exists y method_exists .

class MyClass {
    public $public_field;
    protected $protected_field;
    private $private_field;
    static $static_field;
    const CONSTANT = 0;
    public function public_function() {}
    protected function protected_function() {}
    private function private_function() {}
    static function static_function() {}
}

// check properties
$check = property_exists('MyClass', 'public_field');    // true
$check = property_exists('MyClass', 'protected_field'); // true
$check = property_exists('MyClass', 'private_field');   // true, as of PHP 5.3.0
$check = property_exists('MyClass', 'static_field');    // true
$check = property_exists('MyClass', 'other_field');     // false

// check methods
$check = method_exists('MyClass', 'public_function');    // true
$check = method_exists('MyClass', 'protected_function');    // true
$check = method_exists('MyClass', 'private_function');    // true
$check = method_exists('MyClass', 'static_function');    // true

// however...
$check = property_exists('MyClass', 'CONSTANT');  // false
$check = property_exists($object, 'CONSTANT');    // false

Con una ReflectionClass , también se pueden detectar constantes:

$r = new ReflectionClass('MyClass');
$check = $r->hasProperty('public_field');  // true
$check = $r->hasMethod('public_function'); // true
$check = $r->hasConstant('CONSTANT');      // true
// also works for protected, private and/or static members.

Nota: para property_exists y method_exists , también se puede proporcionar un objeto de la clase de interés en lugar del nombre de la clase. Usando la reflexión, la clase ReflectionObject debe usarse en lugar de ReflectionClass .

Prueba de métodos privados / protegidos

A veces es útil probar métodos privados y protegidos, así como métodos públicos.

class Car
{
    /**
     * @param mixed $argument
     *
     * @return mixed
     */
    protected function drive($argument)
    {
        return $argument;
    }

    /**
     * @return bool
     */
    private static function stop()
    {
        return true;
    }
}

La forma más fácil de probar el método de manejo es usando la reflexión

class DriveTest
{
    /**
     * @test
     */
    public function testDrive()
    {
        // prepare
        $argument = 1;
        $expected = $argument;
        $car = new \Car();

        $reflection = new ReflectionClass(\Car::class);
        $method = $reflection->getMethod('drive');
        $method->setAccessible(true);

        // invoke logic
        $result = $method->invokeArgs($car, [$argument]);

        // test
        $this->assertEquals($expected, $result);
    }
}

Si el método es estático, se pasa nulo en lugar de la instancia de clase.

class StopTest
{
    /**
     * @test
     */
    public function testStop()
    {
        // prepare
        $expected = true;

        $reflection = new ReflectionClass(\Car::class);
        $method = $reflection->getMethod('stop');
        $method->setAccessible(true);

        // invoke logic
        $result = $method->invoke(null);

        // test
        $this->assertEquals($expected, $result);
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow