It's said that a good object keeps private or protected his properties, so nobody can harm the business logic. It's a wise advice, indeed, but sometimes we want to break things: maybe we need to gain access of a private connection managed by an external library, maybe we just want to see the world burn. An example of the first case happened to me a lot of years ago, when I was working on a strange Wordpress integration: I needed the underlying mysqli
connection, but it was stored in a dbh
property of the $wpdb
singleton of class \wpdb
. I couldn't edit the class to make dbh
property public, but I needed the connection. What to do? We all know that a private or protected properties cannot be accessed from outside the instance through ->
operator! A $object->privateProp
will always fail!
That's not entirely true. Quite, but not entirely. There's a little detail that I think it's not so known: the access control to methods and properties is done checking the context from which we are working. So, if we are inside a method of the class, we can access private or protected properties of any instance of that class.
Enough talk, let's do some code. Imagine that we have a third-party library, that we cannot modify, that do this kind of things:
// begin read only code
class DeepThought {
protected $theAnswer = 42;
private $theQuestion = 'How many roads...';
}
$singleton = new DeepThought();
// end read only code
We disperately want to know theAnswer
: we know that is protected, so we can work on it. We know that a protected property can be accessed from extended classes, so we write this piece of code:
class Towel extends DeepThought {
public static function getTheAnswer(DeepThought $instance): int
{
return $instance->theAnswer;
}
}
echo Towel::getTheAnswer($singleton);
Do you see the magic? Towel
extends DeepThought
, so can legitimately access his protected properties. When we call Towel::getTheAnswer()
we are in the Towel
context, so $instance->theAnswer
is also legit.
But theAnswer
is merely protected, and we also want to know theQuestion
, that's private. Extending the class we still have not access to private properties... we need a bigger towel! :-)
Reflection comes to the rescue:
$refl = new ReflectionProperty(DeepThought::class, 'theQuestion');
$refl->setAccessible(true);
echo $refl->getValue($singleton);
Note that Reflection works also on protected properties, but I've presented it last for the sake of narration. We just instantiate a ReflectionProperty for theQuestion
: that's like a tool that can inspect or edit some attributes without changing the code. After we use the reflection to make the property accessible, and lastly we apply our edited property description to the original object. It's like .call()
in JavaScript.
That's all! With these two techniques you can tear apart any class to extract the gems inside. But stay wise! Objects with private properties often have good reasons to be designed that way. No developer take care of backward compatibility for private properties: as in real life, if you play with other's private parts without consent you can get in trouble!
Top comments (2)
I don't know a lick of PHP, I just want to commend you for the best title ever.
Hey i like this trick. did not know :)