Friday, November 11, 2016

Bypassing private and protected visibility in PHP

Members declared protected can be accessed only within the class itself and by inherited classes. Members declared as private may only be accessed by the class that defines the member.

This is true only in an academic sense: code outside the object can still get and set private and protected members. As usual in PHP, all it takes is a little magic.


Let's see how. Start with a completely sealed object, then use some magic to modify it from "outside":

<?php
class Sealed { private $value = 'foo'; }

$sealed = new Sealed;
var_dump($sealed); // private $value => string(3) "foo"

call_user_func(\Closure::bind(
    function () use ($sealed) { $sealed->value = 'BAZ'; },
    null,
    $sealed
));

var_dump($sealed); // private $value => string(3) "BAZ"

The magic lay in \Closure::bind, which allows an anonymous function to bind to a particular class scope. The documentation says:

If an object is given, the type of the object will be used instead. This determines the visibility of protected and private methods of the bound object.

So, effectively, we're adding a run-time setter to $sealed, then calling that setter. This can be elaborated to generic functions that can force set and force get object members:

<?php
function force_set($object, $property, $value) {
    call_user_func(\Closure::bind(
        function () use ($object, $property, $value) {
            $object->{$property} = $value;
        },
        null,
        $object
    ));
}

function force_get($object, $property) {
    return call_user_func(\Closure::bind(
        function () use ($object, $property) {
            return $object->{$property};
        },
        null,
        $object
    ));
}

force_set($sealed, 'value', 'quux');
var_dump(force_get($sealed, 'value')); // 'quux'

I personally use this in testing and debugging, where I need a quick monkey-patch to accomplish a goal without having the formal architecture to support it. But you should probably not rely on this ability for production quality code. Then again, Composer uses this technique so maybe you can, too.


Updated November 12, 2016: A commenter pointed out @ocramius blogged on this in 2013. To differentiate the two articles, I'll add that one can also use this technique to call private or protected methods:

<?php
function force_call($object, $method, ...$args) {
    return call_user_func(\Closure::bind(
        function () use ($object, $method, $args) {
            return call_user_func_array([ $object, $method ], $args);
        },
        null,
        $object
    ));
}

class Sealed {
    private function init($value) {
        return strtoupper($value);
    }
}
$sealed = new Sealed;
echo force_call($sealed, 'init', 'foo'); // string(3) 'FOO'

Updated January 5, 2017: I stumbled upon a similar blog post by Kazuyuki Hayashi today.

Related Posts:

  • PHP Contributor EtiquetteI was the first to publically +1 the Code of Conduct RFC. I'd love to see a policy that fosters diversity and inclusion, because damn the PHP crowd is startlingly similar. But, after hearing the arguments,… Read More
  • Evoking all possible test failure modes in PHPUnitWhen you're writing your own PHPUnit test listener, you need a test case that evokes all the different PHPUnit test states. Here's you go: <?php class EvokesTest extends \PHPUnit_Framework_TestCase { public function t… Read More
  • Using vim to replace string functions with their multi-byte equivalentThe PHP INI option mbstring.func_overload override certain string functions (like strpos, substr, etc.) with multi-byte aware implementations. This makes it super easy to migrate a legacy code base to UTF-8, but immediately r… Read More
  • [Proposed] Elephpant EtiquetteYes, I do believe PHP internals needs a guide to etiquette. But, no, not a code of conduct. Internals is a decades (plural) old cathedral-like meritocracy. There is no benevolent dictator. There is no functional oversight gro… Read More
  • Wielding PHP magic with the Callable Object PatternThe PHP magic method __invoke provides a powerful way to encapsulate functionality while separating state from results and errors. I'm no warlock. I eschew the magic methods PHP offers in favor of explicit method signatures.… Read More

0 comments:

Post a Comment

Share your thoughts!