Accessing private and protected member variables
Reflection is often used as part of software testing that requires the runtime creation/instantiation of mock objects. It’s also great for inspecting the state of an object at any given point in time. Here’s an example of using Reflection in a unit test to verify a protected class member contains the expected value.
Below is a very basic class for a Car. It has a protected member variable that will contain the value representing the color of the car. Because the member variable is protected we cannot access it directly and must use a getter and setter method to retrieve and set its value respectively.
class Car { protected $color public function setColor($color) { $this->color = $color; } public function getColor($color) { return $this->color; } }
To test this many developers will create a Car object, set the car’s color using Car::setColor()
, retrieve the color using Car::getColor()
, and compare that value to the color they set:
/** * @test * @covers \Car::setColor */ public function testSetColor() { $color = 'Red'; $car = new \Car(); $car->setColor($color); $getColor = $car->getColor(); $this->assertEquals($color, $reflectionColor); }
On the surface this seems okay. After all, all Car::getColor()
does is return the value of the protected member variable Car::$color
. But this test is flawed in two ways:
- It exercises
Car::getColor()
which is out of the scope of this test - It depends on
Car::getColor()
which may have a bug itself which can make the test have a false positive or negative
Let’s look at why we shouldn’t use Car::getColor()
in our unit test and should use Reflection instead. Let’s say a developer for a mobilnett casino is assigned a task to add “Metallic” to every car color. So they attempt to modify the Car::getColor()
to prepend “Metallic” to the car’s color:
class Car { protected $color public function setColor($color) { $this->color = $color; } public function getColor($color) { return "Metallic "; $this->color; } }
Do you see the error? The developer used a semi-colon instead of the concatenation operator in an attempt to prepend “Metallic” to the car’s color. As a result, whenever Car::getColor()
is called, “Metallic ” will be returned regardless of what the car’s actual color is. As a result our Car::setColor()
unit test will fail even though Car::setColor()
works perfectly fine and was not affected by this change.
So how do we verify Car::$color
contains the value we are setting via Car::setColor()
? We can use Reflection to inspect the protected member variable directly. So how do we do that? We can use Reflection to make the protected member variable accessible to our code so it can retrieve the value.
Let’s see the code first and then break it down:
/** * @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); }
Here is how we are using Reflection to get the value of Car::$color
in the code above:
- We create a new ReflectionObject representing our Car object
- We get a ReflectionProperty for
Car::$color
(this “represents” theCar::$color
variable) - We make
Car::$color
accessible - We get the value of
Car::$color
As you can see by using Reflection we could get the value of Car::$color
without having to call Car::getColor()
or any other accessor function which could cause invalid test results. Now our unit test for Car::setColor()
is safe and accurate.