Using PHP Closures to access private methods

Context

A couple of years ago, I was dealing with a GDPR data deletion service that relied on an external service to track which clients needed to be processed for data removal. One day, the data deletion job started to intermittently fail due to database deadlocks. The external service however, had already marked the records as deleted, wiping out any trace of affected clients on its end. This was a serious problem, because the actual PII data was scattered across multiple schemas and tables, and now there was no simple way to deal with those missed deletions.

That’s when I reached for a rarely discussed feature of PHP: closures. Closures are a way to invoke private methods buried deep in layers of classes, without having to go through the entire class hierarchy. This made it possible to quickly build a CLI tool to retrigger the deletions.

Normally, the service starts by receiving an id from an external source, translating it to the client’s uuid internal to the service, and then calls a chain of internal methods to actually perform the deletion. Since the external service wiped all traces of the affected clients on its side, that translation step was no longer possible. Using the closure, I invoked the internal deletion routine directly, by manually feeding the uuid’s fetched from the database error logs.

This saved me from having to write some ad-hoc SQL stored procedure to try and force the deletions on the DB side.

Recap on private methods

Private properties and methods are intentionally inaccessible from outside the class. Giving the private property to members of a class is essentially a signal to other developers, that said members are internal implementation details, and should not directly be relied on by other services.

What that means in terms of raw PHP is:

<?php
class Foo {
    private $bar = "Foo and Bar";
    private function add($a, $b) {
        $this->c = $a + $b;
        return $this->c;
    }
}

$foo = new Foo();

$getFooBar = function() {
    return $this->bar;
};

echo $getFooBar();
// => Fatal error: Uncaught Error: Cannot access private property
?>

The attempt to access the private function directly blows up. The closure is not bound to the object’s scope, so PHP enforces its visibility rules and prevents access to the private property.

The trick

PHP allows for $this to be temporarily bound inside a closure to an object instance via the Closure::call() construct. When the closure runs, it basically acts as if it was part of the class, opening up the access to the class’s private members.

This sounds more complicated than what it actually does, so here’s a basic example:

<?php
class Foo {
    private $bar = "Foo and Bar";
    private function add($a, $b) {
        $this->c = $a + $b;
        return $this->c;
    }
}

$foo = new Foo();

$getFooBar = function() {
    return $this->bar;
};

echo $getFooBar->call($foo);

$doAdd = function() {
    return $this->add(...func_get_args());
};

echo $doAdd->call($foo, 10, 5);
?>

The trick is in ->call($foo). It tells PHP to run the closure as if it belonged to $foo.

The ...func_get_args() part is not strictly really necessary for this example, but it is good to keep it in mind, as it lets the closure forward any number of args directly to the private add() method, without hardcoding the parameter names.

Conclusion

That’s all there is to it. This can be a neat little trick when a process gets stuck in a way that is impossible or too tedious to fix by other means.

However, this approach goes directly against the intent of what private members in a class represent, so it is risky to rely on it in a production setting. Internal implementation details of classes may change without any notice, breaking any such code that depends on them.

Use it sparingly, and only when you really need to bypass the conventions to get the job done.