Invading private properties and methods in PHP
Last week, Caleb tweeted about a nifty function called invade
- that he had made to easily work with private properties and methods.
😈 Whatcha think of my new favorite helper method?
— Caleb Porzio (@calebporzio) February 11, 2022
That property/method protected or private? No problemoooo 🔓 pic.twitter.com/HqMXKKpRsJ
He added that invade
function to Livewire. Because I could see myself using this in non-Laravel projects, I packaged up the function in a new package called spatie/invade.
Using the package
Imagine you have this class defined which has a private property and method.
class MyClass
{
private string $privateProperty = 'private value';
private function privateMethod(): string
{
return 'private return value';
}
}
$myClass = new Myclass();
This is how you can get the value of the private property using the invade
function.
invade($myClass)->privateProperty; // returns 'private value'
The invade
function also allows you to change private values.
invade($myClass)->privateProperty = 'changed value';
invade($myClass)->privateProperty; // returns 'changed value
Using invade
, you can also call private functions.
invade($myClass)->privateMethod(); // returns 'private return value'
How the package works under the hood
Accessing private properties and methods seems magical, but it's pretty easy to achieve using reflection. On its reflection classes, PHP has a method setAccessible
that can make private things public in runtime.
The invade
function will pass the given object to an Invader
class. That invader class has magic __get
, __set
and __call
methods that will execute on each interaction with the given object. Before forwarding the call to the object, it will be made accessible.
The source code is so small that I can share it in full in this post.
namespace Spatie\Invade;
use ReflectionClass;
class Invader
{
public object $obj;
public ReflectionClass $reflected;
public function __construct(object $obj)
{
$this->obj = $obj;
$this->reflected = new ReflectionClass($obj);
}
public function __get(string $name): mixed
{
$property = $this->reflected->getProperty($name);
$property->setAccessible(true);
return $property->getValue($this->obj);
}
public function __set(string $name, mixed $value): void
{
$property = $this->reflected->getProperty($name);
$property->setAccessible(true);
$property->setValue($this->obj, $value);
}
public function __call(string $name, array $params = []): mixed
{
$method = $this->reflected->getMethod($name);
$method->setAccessible(true);
return $method->invoke($this->obj, ...$params);
}
}
And that's all there is to it!
Should you use this?
I don't think you should use this in the code of regular projects. In that context, you'll have to design your code in such a way that everything that should be called is easily callable.
Three situations come to mind where I can see invade
being used:
- Inside packages that add specific, magical behaviour, like Livewire
- Inside a package to test some of its own behaviour that is not publicly accessible. The first example that comes to mind is non-public methods inside a package service provider (although it could be argued to make such a method public as well).
- Inside the test suite of package or project where you need to hook into private state/behaviour of other packages. In this case, it might also be a good idea to create an issue on that other package, asking the maintainer to make the state/behaviour accessible in a regular way.
Even though you might argue that it's not a good idea to do so, it's very cool that PHP allows functions like invade
to be created.
Hey! Nice one! It reminds me a project I built a few years back. Its called Communism because, you know, it makes public all private properties :-D
Anyway, I like this project and the non-reflection solution you used. Good stuff!