phparraysmultidimensional-arrayarrayaccess

PHP ArrayAccess set multidimensional


EDIT: I realized the amount of text might be intimidating. The essence of this question:
How to implement ArrayAccess in a way that makes setting multidimensional values possible?

 


 

I am aware that this was discussed here already but I seem unable to implement the ArrayAccess interface correctly.

Basically, I've got a class to handle the app configuration with an array and implemented ArrayAccess. Retrieving values works fine, even values from nested keys ($port = $config['app']['port'];). Setting values works only for one-dimensional arrays, though: As soon as I try to (un)set a value (eg. the port in the previous example), i get the following error message:

Notice:  Indirect modification of overloaded element <object name> has no effect in <file> on <line>

Now the general opinion seems to be that the offsetGet() method has to return by reference (&offsetGet()). That, however, does not solve the problem and I'm afraid I don't know how to implement that method correctly - why is a getter method used to set a value? The php doc here is not really helpful either.

To directly replicate this (PHP 5.4-5.6), please find a sample code attached below:

<?php

class Config implements \ArrayAccess
{

    private $data = array();

    public function __construct($data)
    {
        $this->data = $data;
    }


    /**
     * ArrayAccess Interface
     * 
     */
    public function offsetSet($offset, $value)
    {
        if (is_null($offset)) {
            $this->data[] = $value;
        } else {
            $this->data[$offset] = $value;
        }
    }

    public function &offsetGet($offset)
    {       
        return isset($this->data[$offset]) ? $this->data[$offset] : null;
    }

    public function offsetExists($offset)
    {
        return isset($this->data[$offset]);
    }

    public function offsetUnset($offset)
    {
        unset($this->data[$offset]);
    }
}

$conf = new Config(array('a' => 'foo', 'b' => 'bar', 'c' => array('sub' => 'baz')));
$conf['c']['sub'] = 'notbaz';

 


 

EDIT 2: The solution, as Ryan pointed out, was to use ArrayObject instead (which already implements ArrayAccess, Countable and IteratorAggregate).
To apply it to a class holding an array, structure it like so:

<?php

class Config extends \ArrayObject
{

    private $data = array();

    public function __construct($data)
    {
        $this->data = $data;
        parent::__construct($this->data);
    }


    /**
     * Iterator Interface
     *
     */
    public function getIterator() {
        return new \ArrayIterator($this->data);
    }

    /**
     * Count Interface
     *
     */
    public function count()
    {
        return count($this->data);
    }
}

 

I used this for my Config library libconfig which is available on Github under the MIT license.


Solution

  • I am not sure if this will be useful. I have noticed that the ArrayObject class is 'interesting'...

    I am not sure that this is even an 'answer'. It is more an observation about this class.

    It handles the 'multidimensional array' stuff correctly as standard.

    You may be able to add methods to make it do more of what you wish?

    <?php //
    
    class Config extends \ArrayObject
    {
    
    //    private $data = array();
    
        public function __construct(array $data = array())
        {
            parent::__construct($data);
        }
    }
    
    $conf = new Config(array('a' => 'foo', 'b' => 'bar', 'c' => array('sub' => 'baz')));
    $conf['c']['sub'] = 'notbaz';
    $conf['c']['sub2'] = 'notbaz2';
    
    var_dump($conf, $conf['c'], $conf['c']['sub']);
    
    unset($conf['c']['sub']);
    
    var_dump('isset?: ', isset($conf['c']['sub']));
    
    var_dump($conf, $conf['c'], $conf['c']['sub2']);
    

    Output:

    object(Config)[1]
      public 'a' => string 'foo' (length=3)
      public 'b' => string 'bar' (length=3)
      public 'c' => 
        array
          'sub' => string 'notbaz' (length=6)
          'sub2' => string 'notbaz2' (length=7)
    
    array
      'sub' => string 'notbaz' (length=6)
      'sub2' => string 'notbaz2' (length=7)
    
    string 'notbaz' (length=6)
    
    string 'isset?: ' (length=8)
    
    boolean false
    
    object(Config)[1]
      public 'a' => string 'foo' (length=3)
      public 'b' => string 'bar' (length=3)
      public 'c' => 
        array
          'sub2' => string 'notbaz2' (length=7)
    
    array
      'sub2' => string 'notbaz2' (length=7)
    
    string 'notbaz2' (length=7)