PHP Acceso a variables de miembros privados y protegidos.


Ejemplo

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.