Accessing private PHP class members without reflection
A couple of weeks ago I was working on a very tricky issue on ProxyManager.
The problem is simple: instantiating ReflectionClass or ReflectionProperty is slow, and by slow, I mean really slow!
The reason for this reasearch is that I'm trying to optimize a "hydrator" to work with larger data-sets by still keeping a low initialization overhead.
PHP 5.4 to the rescue!
PHP 5.4 comes with a new API for Closures, which is
Closure#bind()
.
Closure#bind()
basically allows you to get an instance of a closure with the scope of a given
object or class. Neat! That's basically like adding APIs to existing objects!
Let's break some OOP encapsulation to fit our needs.
The techniques to access private members are already explained on the PHP manual, but I am going to make a simplified example anyway.
Here's what you have to do to steal Kitchen#yummy
from following object:
<?php
class Kitchen
{
private $yummy = 'cake';
}
First of all, let's define a closure to read the property as if we had access to it:
<?php
$sweetsThief = function (Kitchen $kitchen) {
return $kitchen->yummy;
}
Let's use it to steal some yummy
stuff from the Kitchen
:
<?php
$kitchen = new Kitchen();
var_dump($sweetsThief($kitchen));
Sadly, this will result in $sweetsThief
being caught with a fatal error that looks like following:
Fatal error: Cannot access private property Kitchen::$yummy in [...] on line [...]
Let's use Closure#bind()
to make our thief smarter:
<?php
$kitchen = new Kitchen();
// Closure::bind() actually creates a new instance of the closure
$sweetsThief = Closure::bind($sweetsThief, null, $kitchen);
var_dump($sweetsThief($kitchen));
Success! We can now get to the cake
!
Changing closure scope vs. Reflection: performance
How does this technique compare with ReflectionProperty#getValue()
? Is it actually faster?
I've built a simple benchmark to profile the "setup" step for this trick over 100000 iterations:
<?php
for ($i = 0; $i < 100000; $i += 1) {
$sweetsThief = Closure::bind(function (Kitchen $kitchen) {
return $kitchen->yummy;
}, null, 'Kitchen');
}
<?php
for ($i = 0; $i < 100000; $i += 1) {
$sweetsThief = new ReflectionProperty('Kitchen', 'yummy');
$sweetsThief->setAccessible(true);
}
On a freshly compiled PHP 5.5 (Ubuntu 13.04 amd64 box), the first script takes around 0.325 seconds to run, while the second one requires 0.658 seconds.
Reflection is much slower here.
That's completely un-interesting though, since nobody will ever instantiate 100000 reflection properties, or at least I cannot find a good reason to do that.
What seems to be more interesting is how accessing properties compares. I've profiled that too:
<?php
$kitchen = new Kitchen();
$sweetsThief = Closure::bind(function (Kitchen $kitchen) {
return $kitchen->yummy;
}, null, 'Kitchen');
for ($i = 0; $i < 100000; $i += 1) {
$sweetsThief($kitchen);
}
<?php
$kitchen = new Kitchen();
$sweetsThief = new ReflectionProperty('Kitchen', 'yummy');
$sweetsThief->setAccessible(true);
for ($i = 0; $i < 100000; $i += 1) {
$sweetsThief->getValue($kitchen);
}
The first script took ~ 0.110 seconds to run, while the second one needed ~ 0.199 seconds!
We are actually much faster than reflection! Impressive!
Accessing private class properties by reference
There's actually one big advantage in using a Closure instead of ReflectionProperty, which is that you can now retrieve a private property by reference!
<?php
$sweetsThief = Closure::bind(function & (Kitchen $kitchen) {
return $kitchen->yummy;
}, null, $kitchen);
$cake = & $sweetsThief($kitchen);
$cake = 'lie';
var_dump('the cake is a ' . $sweetsThief($kitchen));
A generic property reader abstraction
With all these new concepts we can write a very simplified accessor that allows us to read any property of any object:
<?php
$reader = function & ($object, $property) {
$value = & Closure::bind(function & () use ($property) {
return $this->$property;
}, $object, $object)->__invoke();
return $value;
};
$kitchen = new Kitchen();
$cake = & $reader($kitchen, 'cake');
$cake = 'sorry, I ate it!';
var_dump($kitchen);
Here's the working example.
That's it: accessing any property, anywhere, and that even by reference! Success! We have broken the rules once again!
I won't cover the "writing properties" part, nor handling inherited private properties, since that's just details of this basic trick that need more code and are un-interesting to us.
Conclusion
Yet another time, PHP shows its best and worst aspects all together. It's a horrible language with a horrible syntax, but it allows us to write amazing code and to run around a huge number of language limitations by just providing new and awesome features at every release!
I won't use this technique myself, but it was interesting to dive into it, and it will become useful again if I need to get references to private/protected class members in my crazy proxy projects.
I should hereby add a disclaimer: use with caution!
Errata
In the first version of the article that was published 2013-07-10, I actually stated that Reflection was faster: that's not true and is the result of a mistake that I made while running the tests, since I was running a PHP version with loads of extensions that were affecting the results.
I have created a virtual machine with a clean 5.5 PHP build to get accurate results, which demonstrate that Reflection is actually slower than closures in every case.
I also wrote a very small set of benchmarks that you may find in the blog repository