Overloading arrays in PHP 5.2.0
Update: I ran into issues with the ArrayObject
solution, as there was a bug in PHP 5.2.0 (now fixed) with its interaction with empty()
and isset()
when used with the ARRAY_AS_PROPS
flag. I tried a number of fixes, but eventually my friend Mike pointed out something I'd missed: as of PHP 5.1, setting undefined public properties no longer raises an E_STRICT
notice. Knowing this, you can now do the following without raising any errors:
class Foo
{
public function __set($key, $value)
{
$this->$key = $value;
}
}
$foo = new Foo();
$foo->bar = array();
$foo->bar[] = 42;
This is a much simpler solution, performs better, and solves all the issues I was presented. Thanks, Mike!
Several weeks back, a bug was reported against Zend_View that had me initially stumped. Basically, the following was now failing in PHP 5.2.0:
$view->foo = array();
$view->foo[] = 42;
A notice was raised stating, "Notice: Indirect modification of overloaded property Zend_View::$foo has no effect."
I'd read about this some months back on the php internals list, but at the time hadn't understood the consequences. Basically, __get()
no longer returns a reference and returns values in read mode, which makes modifying arrays using overloading impossible using traditional methods.
Derick Rethans blogged about the issue in August. His solution was to use a switch()
statement in __get()
to cast the returned value explicitly as an array:
public function __get($key)
{
if (is_array($this->_vars[$key])) {
return (array) $this->_vars[$key];
}
return $this->_vars[$key];
}
The problem with this approach is that you then have issues with other array functionalities, such as assigning by reference.
After some work, I found the best solution was to have the class extend ArrayObject
, but with a slight twist:
class My_Class extends ArrayObject
{
public function __construct($config = array())
{
// ... some setup
// Allow accessing properties as either array keys or object properties:
parent::__construct(array(), ArrayObject::ARRAY_AS_PROPS);
}
}
This combination allows some very flexible access to properties in the object:
// from the original example:
$view->foo = array();
$view->foo[] = 42;
echo $view['foo'][0]; // '42'
echo $view->foo[0]; // same
One issue that was always difficult to work with in Zend_View
was keeping 'public' properties — template variables — separate from private/protected properties (things like the helper, filter, and script paths). Since those properties are pre-declared in the class, the ArrayObject::ARRAY_AS_PROPS
setting prevented any such collision from happening — and helped simplify the code.
Moral of the story? If you need to be able to modify overloaded arrays in your class, and support PHP 5.2.0, extend ArrayObject
.