Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ArrayAccess multidimensional (un)set?

I have a class implementing ArrayAccess and I'm trying to get it to work with a multidimensional array. exists and get work. set and unset are giving me a problem though.

class ArrayTest implements ArrayAccess {
    private $_arr = array(
        'test' => array(
            'bar' => 1,
            'baz' => 2
        )
    );
    
    public function offsetExists($name) {
        return isset($this->_arr[$name]);
    }
    
    public function offsetSet($name, $value) {
        $this->_arr[$name] = $value;
    }
    
    public function offsetGet($name) {
        return $this->_arr[$name];
    }
    
    public function offsetUnset($name) {
        unset($this->_arr[$name]);
    }
}

$arrTest = new ArrayTest();


isset($arrTest['test']['bar']);  // Returns TRUE

echo $arrTest['test']['baz'];    // Echo's 2

unset($arrTest['test']['bar']);   // Error
$arrTest['test']['bar'] = 5;     // Error

I know $_arr could just be made public so you could access it directly, but for my implementation it's not desired and is private.

The last 2 lines throw an error: Notice: Indirect modification of overloaded element.

I know ArrayAccess just generally doesn't work with multidimensional arrays, but is there anyway around this or any somewhat clean implementation that will allow the desired functionality?

The best idea I could come up with is using a character as a separator and testing for it in set and unset and acting accordingly. Though this gets really ugly really fast if you're dealing with a variable depth.

Does anyone know why exists and get work so as to maybe copy over the functionality?

Thanks for any help anyone can offer.

like image 888
anomareh Avatar asked May 21 '10 10:05

anomareh


People also ask

How PHP multidimensional array works?

A multidimensional array is an array containing one or more arrays. PHP supports multidimensional arrays that are two, three, four, five, or more levels deep. However, arrays more than three levels deep are hard to manage for most people.

What is array what is it's type and explain multidimensional array with example in PHP?

The first method is to assign each value to a single variable, while the second method is to assign multiple values to a single variable, which is much more effective. An array is what we term it. A multidimensional array in PHP is a data structure that allows you to store multiple values in a single variable.

How can create multidimensional array in array in PHP?

You create a multidimensional array using the array() construct, much like creating a regular array. The difference is that each element in the array you create is itself an array. For example: $myArray = array( array( value1 , value2 , value3 ), array( value4 , value5 , value6 ), array( value7 , value8 , value9 ) );

What is nested array in PHP?

In the above example Create a variable $arr with value an nested array. Here at the place of array's first element there is also an array with three value like $arr[0][0]=10, $arr[0][1]=10, and $arr[0][2]=10. at the second index the value are : $arr[1][0]=10, $arr[1][1]=10, and $arr[1][2]=10.


2 Answers

The problem could be resolved by changing public function offsetGet($name) to public function &offsetGet($name) (by adding return by reference), but it will cause Fatal Error ("Declaration of ArrayTest::offsetGet() must be compatible with that of ArrayAccess::offsetGet()").

PHP authors screwed up with this class some time ago and now they won't change it in sake of backwards compatibility:

We found out that this is not solvable without blowing up the interface and creating a BC or providing an additional interface to support references and thereby creating an internal nightmare - actually i don't see a way we can make that work ever. Thus we decided to enforce the original design and disallow references completley.

Edit: If you still need that functionality, I'd suggest using magic method instead (__get(), __set(), etc.), because __get() returns value by reference. This will change syntax to something like this:

$arrTest->test['bar'] = 5;

Not an ideal solution of course, but I can't think of a better one.

Update: This problem was fixed in PHP 5.3.4 and ArrayAccess now works as expected:

Starting with PHP 5.3.4, the prototype checks were relaxed and it's possible for implementations of this method to return by reference. This makes indirect modifications to the overloaded array dimensions of ArrayAccess objects possible.

like image 74
Alexander Konstantinov Avatar answered Oct 22 '22 21:10

Alexander Konstantinov


This issue is actually solvable, entirely functional how it should be.

From a comment on the ArrayAccess documentation here:

<?php

// sanity and error checking omitted for brevity
// note: it's a good idea to implement arrayaccess + countable + an
// iterator interface (like iteratoraggregate) as a triplet

class RecursiveArrayAccess implements ArrayAccess {

    private $data = array();

    // necessary for deep copies
    public function __clone() {
        foreach ($this->data as $key => $value) if ($value instanceof self) $this[$key] = clone $value;
    }

    public function __construct(array $data = array()) {
        foreach ($data as $key => $value) $this[$key] = $value;
    }

    public function offsetSet($offset, $data) {
        if (is_array($data)) $data = new self($data);
        if ($offset === null) { // don't forget this!
            $this->data[] = $data;
        } else {
            $this->data[$offset] = $data;
        }
    }

    public function toArray() {
        $data = $this->data;
        foreach ($data as $key => $value) if ($value instanceof self) $data[$key] = $value->toArray();
        return $data;
    }

    // as normal
    public function offsetGet($offset) { return $this->data[$offset]; }
    public function offsetExists($offset) { return isset($this->data[$offset]); }
    public function offsetUnset($offset) { unset($this->data); }

}

$a = new RecursiveArrayAccess();
$a[0] = array(1=>"foo", 2=>array(3=>"bar", 4=>array(5=>"bz")));
// oops. typo
$a[0][2][4][5] = "baz";

//var_dump($a);
//var_dump($a->toArray());

// isset and unset work too
//var_dump(isset($a[0][2][4][5])); // equivalent to $a[0][2][4]->offsetExists(5)
//unset($a[0][2][4][5]); // equivalent to $a[0][2][4]->offsetUnset(5);

// if __clone wasn't implemented then cloning would produce a shallow copy, and
$b = clone $a;
$b[0][2][4][5] = "xyzzy";
// would affect $a's data too
//echo $a[0][2][4][5]; // still "baz"

?>

You can then extend that class, like so:

<?php

class Example extends RecursiveArrayAccess {
    function __construct($data = array()) {
        parent::__construct($data);
    }
}

$ex = new Example(array('foo' => array('bar' => 'baz')));

print_r($ex);

$ex['foo']['bar'] = 'pong';

print_r($ex);

?>

This will give you an object that can be treated like an array (mostly, see note in code), which supports multi-dimensional array set/get/unset.

like image 6
Dakota Avatar answered Oct 22 '22 22:10

Dakota