PHP Accesso a variabili membro private e protette


Esempio

Reflection viene spesso utilizzato come parte del test del software, ad esempio per la creazione / creazione di runtime di oggetti mock. È anche ottimo per controllare lo stato di un oggetto in qualsiasi momento. Ecco un esempio dell'utilizzo di Reflection in un unit test per verificare che un membro di classe protetto contenga il valore previsto.

Di seguito è riportata una classe di base per un'auto. Ha una variabile membro protetta che conterrà il valore che rappresenta il colore dell'auto. Poiché la variabile membro è protetta, non è possibile accedervi direttamente e utilizzare un metodo getter e setter per recuperare e impostare il suo valore rispettivamente.

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

Per testare questo, molti sviluppatori creeranno un oggetto Car, impostano il colore Car::setColor() usando Car::setColor() , recuperano il colore usando Car::getColor() e confrontano tale valore con il colore impostato:

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

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

In superficie, questo sembra ok. Dopotutto, tutto ciò che fa Car::getColor() restituisce il valore della variabile membro protetta Car::$color . Ma questo test è difettoso in due modi:

  1. Esercita Car::getColor() che non rientra nell'ambito di questo test
  2. Dipende da Car::getColor() che può avere un bug stesso che può rendere il test un falso positivo o negativo

Diamo un'occhiata al motivo per cui non dovremmo usare Car::getColor() nel nostro test unitario e dovremmo usare Reflection. Supponiamo che a uno sviluppatore venga assegnata un'attività che aggiunge "Metallico" a ogni colore della vettura. Quindi tentano di modificare Car::getColor() per anteporre "Metallic" al colore dell'auto:

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

Vedi l'errore? Lo sviluppatore ha usato un punto e virgola anziché l'operatore di concatenazione nel tentativo di anteporre "Metallico" al colore della vettura. Di conseguenza, ogni volta che viene chiamato Car::getColor() , "Metallic" viene restituito indipendentemente da quale sia il colore effettivo dell'auto. Di conseguenza, il test dell'unità Car::setColor() fallirà anche se Car::setColor() funziona perfettamente e non è influenzato da questa modifica .

Quindi, come possiamo verificare che Car::$color contenga il valore che stiamo impostando tramite Car::setColor() ? Possiamo usare la Refelection per ispezionare direttamente la variabile membro protetta. Quindi, come possiamo farlo? Possiamo usare Refelection per rendere la variabile membro protetta accessibile al nostro codice in modo che possa recuperare il valore.

Vediamo prima il codice e poi scomporlo:

/**
 * @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);
}

Ecco come utilizziamo Reflection per ottenere il valore di Car::$color nel codice qui sopra:

  1. Creiamo un nuovo ReflectionObject che rappresenta il nostro oggetto Car
  2. Otteniamo una ReflectionProperty per Car::$color (questo "rappresenta" la variabile Car::$color )
  3. Facciamo Car::$color accessibile
  4. Otteniamo il valore di Car::$color

Come potete vedere usando Reflection, potremmo ottenere il valore di Car::$color senza dover chiamare Car::getColor() o qualsiasi altra funzione di accesso che potrebbe causare risultati di test non validi. Ora il nostro test unitario per Car::setColor() è sicuro e preciso.