PHPnews.io

Invading private properties and methods in PHP

Written by murze.be / Original link on Feb. 14, 2022

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?
That property/method protected or private? No problemoooo 🔓 pic.twitter.com/HqMXKKpRsJ

— Caleb Porzio (@calebporzio) February 11, 2022

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.

classMyClass
{
privatestring $privateProperty ='private value';

privatefunctionprivateMethod():string
    {
return'private return value';
    }
}

$myClass =newMyclass();

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.

namespaceSpatie\Invade;

useReflectionClass;

classInvader
{
publicobject $obj;
publicReflectionClass $reflected;

publicfunction__construct(object $obj)
    {
$this->obj = $obj;
$this->reflected =newReflectionClass($obj);
    }

publicfunction__get(string $name):mixed
    {
        $property =$this->reflected->getProperty($name);

        $property->setAccessible(true);

return $property->getValue($this->obj);
    }

publicfunction__set(string $name, mixed $value):void
    {
        $property =$this->reflected->getProperty($name);

        $property->setAccessible(true);

        $property->setValue($this->obj, $value);
    }

publicfunction__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:

  1. Inside packages that add specific, magical behaviour, like Livewire
  2. 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).
  3. 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.

murze

« Broadcasting Events in Laravel - Using Scout APM to Monitor a Laravel Application »