Zoeken…


Toegang tot private en beschermde lidvariabelen

Reflectie wordt vaak gebruikt als onderdeel van het testen van software, zoals voor het maken / instantiëren van runtime-objecten. Het is ook geweldig voor het inspecteren van de status van een object op een bepaald tijdstip. Hier is een voorbeeld van het gebruik van Reflectie in een eenheidstest om te controleren of een beschermd klasse-lid de verwachte waarde bevat.

Hieronder staat een heel eenvoudige les voor een auto. Het heeft een beschermde lidvariabele die de waarde zal bevatten die de kleur van de auto vertegenwoordigt. Omdat de lidvariabele beveiligd is, hebben we er geen directe toegang toe en moeten we een methode getter en setter gebruiken om de waarde respectievelijk op te halen en in te stellen.

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

Om dit te testen zullen veel ontwikkelaars een Car-object maken, de kleur van de auto instellen met Car::setColor() , de kleur ophalen met Car::getColor() en die waarde vergelijken met de kleur die ze instellen:

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

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

Op het eerste gezicht lijkt dit oké. Car::getColor() doet immers alleen de waarde van de beschermde lidvariabele Car::$color . Maar deze test is op twee manieren gebrekkig:

  1. Het oefent Car::getColor() die buiten het bereik van deze test valt
  2. Het hangt af van Car::getColor() die zelf een fout kan hebben waardoor de test een vals positief of negatief kan hebben

Laten we eens kijken waarom we Car::getColor() in onze unit-test zouden moeten gebruiken en in plaats daarvan Reflection zouden moeten gebruiken. Stel dat een ontwikkelaar een taak krijgt toegewezen om 'Metallic' aan elke autokleur toe te voegen. Dus proberen ze de Car::getColor() aan te passen om "Metallic" voor de kleur van de auto te plaatsen:

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

Zie je de fout? De ontwikkelaar gebruikte een puntkomma in plaats van de operator voor aaneenschakeling in een poging "Metallic" voor te bereiden op de kleur van de auto. Als gevolg hiervan wordt, telkens Car::getColor() wordt genoemd, "Metallic" geretourneerd, ongeacht de werkelijke kleur van de auto. Als gevolg hiervan zal onze Car::setColor() -test mislukken , hoewel Car::setColor() prima werkt en niet door deze wijziging werd beïnvloed .

Dus hoe controleren we of Car::$color de waarde bevat die we instellen via Car::setColor() ? We kunnen Refelection gebruiken om de beschermde lidvariabele rechtstreeks te inspecteren. Dus hoe doen we dat ? We kunnen Refelection gebruiken om de beschermde lidvariabele toegankelijk te maken voor onze code zodat deze de waarde kan ophalen.

Laten we eerst de code bekijken en deze vervolgens opsplitsen:

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

Dit is hoe we Reflection gebruiken om de waarde Car::$color in de bovenstaande code:

  1. We maken een nieuw ReflectionObject dat ons Car-object vertegenwoordigt
  2. We krijgen een ReflectionProperty voor Car::$color (dit "vertegenwoordigt" de Car::$color variabele)
  3. We maken Car::$color toegankelijk
  4. We krijgen de waarde Car::$color

Zoals u kunt zien met Reflection, kunnen we de waarde Car::$color zonder Car::getColor() of een andere accessor-functie te hoeven gebruiken die ongeldige testresultaten kan veroorzaken. Nu is onze eenheidstest voor Car::setColor() veilig en nauwkeurig.

Functiedetectie van klassen of objecten

method_exists van klassen kan gedeeltelijk worden gedaan met de functies property_exists en 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

Met een ReflectionClass kunnen ook constanten worden gedetecteerd:

$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.

Opmerking: voor property_exists en method_exists kan in plaats van de method_exists ook een object van de gewenste klasse worden opgegeven. Met behulp van reflectie moet de klasse ReflectionObject worden gebruikt in plaats van ReflectionClass .

Particuliere / beschermde methoden testen

Soms is het handig om zowel privé- als beschermde methoden te testen.

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

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

De eenvoudigste manier om de ritmethode te testen, is reflectie gebruiken

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);
    }
}

Als de methode statisch is, geeft u null door in plaats van de klasse-instantie

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow