Marco Pivetta (Ocramius)

Property Accessors in PHP Userland

Some time ago many people were disappointed by the fact that PHP wasn't going to have property accessors, which are a very cool feature and would have helped a lot in reducing boilerplate code in a lot of applications and frameworks.

Last October I learned from Lukas Smith about a trick that he used to discover when a public property of an object was being accessed.
He mainly used the trick that I'm going to show to handle lazy-loading within PHPCR ODM.

The trick basically allows to implement property accessors in userland code by exploiting how PHP handles object properties internally.

I never got back to writing a blogpost about it, but here it finally is!

Here's a very simple example:

<?php

class Foo
{
    public $publicProperty = 'baz';
}

What we want is some way to know whenever somebody writes or reads property $publicProperty from our object.

In order to do so, we can use a simple wrapper for our Foo object. Because we want to respect the LSP, we have this wrapper extending Foo:

<?php

class FooWrapper extends Foo
{
    public function __construct()
    {
        unset($this->publicProperty);
    }
}

That's it so far! Let's try it out:

<?php

$foo = new FooWrapper();

echo $foo->publicProperty;

Weirdly, this will produce something like following:

Notice: Undefined property: FooWrapper::$publicProperty in [...]

That basically means the property was not just set to NULL, but completely removed!

Let's use this at our own advantage by tweaking FooWrapper a bit:

<?php

class FooWrapper extends Foo
{
    private $wrapped;

    public function __construct(Foo $wrapped)
    {
        $this->wrapped = $wrapped;

        unset($this->publicProperty);
    }

    public function __get($name)
    {
        echo 'Getting property ' . $name . PHP_EOL;

        return $this->wrapped->$name;
    }

    public function __set($name, $value)
    {
        echo 'Setting property ' . $name . ' to ' . $value . PHP_EOL;

        return $this->wrapped->$name = $value;
    }
}

And here again, let us try it out:

<?php

$foo = new FooWrapper(new Foo());

echo $foo->publicProperty;
echo PHP_EOL;
echo $foo->publicProperty = 'test';

This will produce following output:

Getting property publicProperty
baz
Setting property publicProperty to test
test

Cool, huh? And the same works with __isset and __unset too!

This doesn't really replace property accessors, but it gives us a way of protecting access to public properties via composition, inheritance and a bit of hacking.

There's not many use cases for this right now, since you have to write a lot of boilerplate code for it to work correctly.

It is worth mentioning that this logic has been used to make Doctrine 2.4 even more awesome. I also wrote a component called ProxyManager, which avoids you from writing all the boilerplate code over and over again, so check it out!

Here's how the code from before rewritten using ProxyManager 0.4:

<?php

use ProxyManager\Configuration;
use ProxyManager\Factory\AccessInterceptorValueHolderFactory as Factory;

require_once __DIR__ . '/vendor/autoload.php';

class Foo
{
    public $publicProperty = 'baz';
}

$config  = new Configuration();
$factory = new Factory($config);

$foo = $factory->createProxy(
    new Foo(),
    array(
        '__get' => function ($proxy, $instance, $method, $params) {
            echo 'Getting property ' . $params['name'] . PHP_EOL;
        },
        '__set' => function ($proxy, $instance, $method, $params) {
            echo 'Setting property ' . $params['name'] . ' to ' . $params['value'] . PHP_EOL;
        }
    )
);

echo $foo->publicProperty;
echo PHP_EOL;
echo $foo->publicProperty = 'test';

Give it a try and drop me a line if you like it or hate it!

Tags: php, oop, access, property accessors