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!