PHP
Reflexion
Suche…
Zugriff auf private und geschützte Mitgliedervariablen
Reflection wird häufig im Rahmen von Softwaretests verwendet, z. B. zur Laufzeiterstellung / Instantiierung von Scheinobjekten. Es ist auch großartig, um den Status eines Objekts zu einem bestimmten Zeitpunkt zu überprüfen. Hier ein Beispiel für die Verwendung von Reflection in einem Komponententest, um zu überprüfen, ob ein geschützter Klassenmitglied den erwarteten Wert enthält.
Nachfolgend finden Sie eine sehr grundlegende Klasse für ein Auto. Es hat eine geschützte Elementvariable, die den Wert enthält, der die Farbe des Autos darstellt. Da die Membervariable geschützt ist, können wir nicht direkt darauf zugreifen und müssen eine Getter- und Setter-Methode verwenden, um ihren Wert abzurufen bzw. festzulegen.
class Car
{
protected $color
public function setColor($color)
{
$this->color = $color;
}
public function getColor($color)
{
return $this->color;
}
}
Um dies zu testen, erstellen viele Entwickler ein Car-Objekt, legen die Farbe des Autos mit Car::setColor()
, Car::setColor()
die Farbe mit Car::getColor()
und vergleichen diesen Wert mit der von ihnen festgelegten Farbe:
/**
* @test
* @covers \Car::setColor
*/
public function testSetColor()
{
$color = 'Red';
$car = new \Car();
$car->setColor($color);
$getColor = $car->getColor();
$this->assertEquals($color, $reflectionColor);
}
Oberflächlich scheint das okay zu sein. Schließlich Car::getColor()
den Wert der geschützten Membervariablen Car::$color
. Dieser Test ist jedoch auf zwei Arten fehlerhaft:
- Es übt
Car::getColor()
was sich außerhalb dieses Tests befindet - Es hängt von
Car::getColor()
das einen Fehler enthalten kann, der dazu führen kann, dass der Test falsch positiv oder negativ ist
Schauen wir uns an, warum wir Car::getColor()
in unserem Unit-Test nicht verwenden sollten und stattdessen Reflection verwenden sollten. Angenommen, ein Entwickler erhält die Aufgabe, jeder Fahrzeugfarbe "Metallic" hinzuzufügen. Sie versuchen also, Car::getColor()
zu modifizieren, um "Metallic" der Farbe des Autos Car::getColor()
:
class Car
{
protected $color
public function setColor($color)
{
$this->color = $color;
}
public function getColor($color)
{
return "Metallic "; $this->color;
}
}
Sehen Sie den Fehler? Der Entwickler verwendete ein Semikolon anstelle des Verkettungsoperators, um "Metallic" vor die Farbe des Autos zu setzen. Car::getColor()
, wenn Car::getColor()
aufgerufen wird, wird "Metallic" zurückgegeben, unabhängig von der tatsächlichen Farbe des Fahrzeugs. Daher Car::setColor()
fehl , obwohl Car::setColor()
einwandfrei funktioniert und von dieser Änderung nicht betroffen war .
Wie können wir also überprüfen, ob Car::$color
den Wert enthält, den wir über Car::setColor()
? Wir können Refelection verwenden, um die geschützte Elementvariable direkt zu prüfen. Wie machen wir das ? Wir können Refelection verwenden, um die geschützte Membervariable für unseren Code zugänglich zu machen, damit der Wert abgerufen werden kann.
Sehen wir uns zuerst den Code an und zerlegen Sie ihn:
/**
* @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);
}
So verwenden wir Reflection, um den Wert von Car::$color
im obigen Code zu ermitteln:
- Wir erstellen ein neues ReflectionObject, das unser Car-Objekt darstellt
- Wir erhalten eine ReflectionProperty für
Car::$color
(dies repräsentiert die VariableCar::$color
). - Wir machen
Car::$color
zugänglich - Wir erhalten den Wert von
Car::$color
Wie Sie mit Reflection sehen können, könnten wir den Wert von Car::$color
ohne Car::getColor()
oder eine andere Accessor-Funktion aufrufen zu müssen, die ungültige Testergebnisse verursachen könnte. Jetzt ist unser Car::setColor()
für Car::setColor()
sicher und genau.
Featureerkennung von Klassen oder Objekten
Die method_exists
von Klassen kann teilweise mit den Funktionen property_exists
und method_exists
werden.
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
Mit einer ReflectionClass
können auch Konstanten erkannt werden:
$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.
Hinweis: Für property_exists
und method_exists
kann anstelle des method_exists
auch ein Objekt der interessierenden Klasse angegeben werden. Bei Verwendung der Reflektion sollte die ReflectionObject
Klasse anstelle von ReflectionClass
.
Testen privater / geschützter Methoden
Manchmal ist es nützlich, private und geschützte Methoden sowie öffentliche Methoden zu testen.
class Car
{
/**
* @param mixed $argument
*
* @return mixed
*/
protected function drive($argument)
{
return $argument;
}
/**
* @return bool
*/
private static function stop()
{
return true;
}
}
Die einfachste Möglichkeit, die Testmethode zu testen, ist die Reflektion
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);
}
}
Wenn die Methode statisch ist, übergeben Sie null anstelle der Klasseninstanz
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);
}
}