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.

Like this content?

Become a Patron!