I was discussing this technique during a code review recently, and I realized this cool technique might not be well known. I discovered it quite by accident a few years ago.
Sometimes, strictly for testing purposes, we might need to access a private or protected property or method. Our usual inclination is to use Reflection to do this. Reflection is a bit cumbersome, though, because there’s a lot of extra boilerplate to set it up.
bind method, we can do the same thing in PHP.
Let’s start with a sample class:
We know how to read and change
$me->name. It’s public, so there’s no problem there.
Now, what if we need, somewhere in a test, to set the exact age. That’s a protected property, so we can’t just directly change it, but Closures can give us a way around that.
(fn (int $newAge) => $this->age = $newAge)->call($me, 30); // Changes $me->age to 30
So, what did we do here? Let’s break it down a bit.
$changeAge = (fn (int $newAge) => $this->age = $newAge);
First, we create a closure that changes
$this->age to whatever is passed to it. If we tried to do
$changeAge(30), we’d get an error, though, because we defined this outside of the class.
$this doesn’t actually exist here, or, if we put this code in a unit test,
$this would refer to the unit test class itself.
call comes in.
Closure::call binds a closure to a new object, and calls it with whatever args you passed to it. In other words, the first argument passed to
call become the
$this within the closure. Any other arguments are passed to the function itself.
We can also simply access the
$cool properties without changing them. So, I can do:
$amICool = (fn () => $this->cool)->call($me); // true
That works great for the non-static properties or methods. What about the static ones?
$mySpecies = (fn () => static::$species)->bindTo(null, Person::class)(); // homo sapien
So, this is a little different. First, we’re using
Closure::bindTo which returns a new closure, which has been re-bound to the specified object or class. With
bindTo, you can pass an object (like
call), or you can leave that null and pass a class instead, which changes the static binding. That’s what we’ve done here. And since it returns a new closure, you then have to call it, which is why we have an extra
So, let’s break this down step-by-step as well.
$getSpecies = (fn () => static::$species);
A closure which returns that
$species of the current scope.
$boundGetSpecies = $getSpecies->bindTo(null, Person::class);
A new closure, which changes the scope to
Person, meaning any references to
static refer to
$mySpecies = $boundGetSpecies();
Or, if we to change the species to an arbitrary value, as we did in the third one:
// Create species changing closure, initially bound to the current scope
You can use
bindTo in a similar way to
(fn () => $this->birthday())->bindTo($me)();
Let’s break it down.
$haveABirthday = (fn () => $this->birthday());
Closure which calls
birthday() on the object within the current scope.
$haveMyBirthday = $haveABirthday->bindTo($me);
Create a new closure with
$me as the scope. This way, any reference to
$this refers to
$haveMyBirthday is a closure, we have to actually call it.
I actually use this in a package I recently wrote that should make this even easier.
And to reiterate, I do not recommend doing this in production code. There’s a reason visibility exists. We shouldn’t circumvent it like this in code on our server. But, if we need to fiddle around with some objects for testing, this technique can simplify that for us.