Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement Iterator interface for ArrayAccess without container array

This is my attempt at implementing https://www.php.net/manual/en/class.iterator.php for an ArrayAccess. Many examples use a container array as a private member variable; but I do not want to use a container array if possible. The main reason why I don't want a container array is because I'd like to access the property (array key) like this $DomainData->domainId all while having intellisense, etc.

Demo: https://ideone.com/KLPwwY

class DomainData implements ArrayAccess, Iterator
{
    private $position = 0;
    public $domainId;
    public $color;

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

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

    public function offsetSet($offset, $value)
    {
        $this->$offset = $value;
    }

    public function offsetGet($offset)
    {
        return $this->$offset;
    }

    public function offsetUnset($offset)
    {
        $this->$offset = null;
    }

    /*****************************************************************/
    /*                     Iterator Implementation                   */
    /*****************************************************************/

    public function rewind()
    {
        $this->position = 0;
    }

    public function current()
    {
        return $this[$this->position];
    }

    public function key()
    {
        return $this->position;
    }

    public function next()
    {
        ++$this->position;
    }

    public function valid()
    {
        return isset($this[$this->position]);
    }
}

Calling it:

$domainData = new DomainData([
    "domainId" => 1,
    "color" => "red"
]);

var_dump($domainData);

foreach($domainData as $k => $v){
    var_dump("domainData[$k] = $v");
}

actual:

object(DomainData)#1 (3) {
  ["position":"DomainData":private]=>
  int(0)
  ["domainId"]=>
  int(1)
  ["color"]=>
  string(3) "red"
}

desired:

object(DomainData)#1 (3) {
  ["position":"DomainData":private]=>
  int(0)
  ["domainId"]=>
  int(1)
  ["color"]=>
  string(3) "red"
}
string(24) "domainData[domainId] = 1"
string(23) "domainData[color] = red"
like image 981
ParoX Avatar asked Jun 09 '26 17:06

ParoX


1 Answers

Let me describe a couple of ways of how you could do this.

ArrayObject with custom code

ArrayObject implements all of the interfaces that you want.

class DomainData extends ArrayObject
{
  public $domainId;
  public $color;

  public function __construct($data = array())
  {
    parent::__construct($data);
    foreach ($data as $key => $value) {
      $this->$key = $value;
    }
  }
}

This isn't very nice, though; it copies the keys and values twice, and changing a property doesn't change the underlying array.

Implement IteratorAggregate on get_object_vars()

If you don't mind giving up on ArrayAccess, you could get away with only implementing an aggregate iterator.

class DomainData implements IteratorAggregate
{
    public $domainId;
    public $color;

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

    public function getIterator()
    {
        return new ArrayIterator(get_object_vars($this));
    }
}

ArrayObject with property flag and doc blocks

A better way would be to use doc blocks for describing your properties (described here), and then use the ARRAY_AS_PROPS flag to expose the array as properties.

/**
 * Magic class
 * @property int $domainId
 * @property string $color
 */
class DomainData extends ArrayObject
{
  function __construct($data = []) {
    parent::__construct($data, parent::ARRAY_AS_PROPS);
  }
}

When loaded inside PhpStorm, you'd see this: phpstorm editor

like image 104
Ja͢ck Avatar answered Jun 11 '26 08:06

Ja͢ck