Search code examples
phparraysvariadic

Using object implementing ArrayAccess and Iterator as variadic parameter


I have a class that implements both ArrayAccess and Iterator.

I'm trying to figure out how to pass this object variadic parameter to a native function like array_merge:

array_merge(...$object);

To my disappointment I'm getting an error saying that $object is not an array.

array_merge(): Argument #1 is not an array

I've looked at these other interfaces but non of them seem obvious: IteratorAggregate, Serializable, Countable. Also ArrayObject turned out to be a dead end.

I do have a getter for to convert to array. But I was kind of hopping to discover my $object transform into an array just by implementing either ArrayAccess or Iterator, since it is about unfolding the array.

Is there another interface I can implement to make my class be more array-like?


Solution

  • This is a new language feature as documented in the migration guide from 5.5.x to 5.6.x in the manual (section Argument unpacking via ...), you must be on a pre-5.6.x runtime.

    If you cannot upgrade your runtime, you must make use of your getter for converting it to an array (similar to ArrayObject's getArrayCopy):

    call_user_func_array('array_merge', $arr->getArrayCopy());
    

    Tests

    The code below (based on PHP's documentation examples for ArrayAccess and Iterator) executed successfully on PHP 5.6.2, 5.6.17 and 7.0.1. It fails indeed on older versions (5.5.31 and older).

    $arr = new MyArray();
    $arr[0] = array(1, 2);
    $arr[1] = array(3, 4);
    
    // MyArray
    print(get_class($arr));
    
    // Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 ) 
    print_r(array_merge(...$arr));
    

    Implementation of MyArray:

    class MyArray implements ArrayAccess, Iterator
    {
          private $container = array();
          private $position = 0;
    
          public function getArrayCopy() {
            return $this->container;
          }
    
          public function offsetSet($offset, $value) {
            if (is_null($offset)) {
                $this->container[] = $value;
            } else {
                $this->container[$offset] = $value;
            }
          }
    
          public function offsetExists($offset) {
            return isset($this->container[$offset]);
          }
    
          public function offsetUnset($offset) {
            unset($this->container[$offset]);
          }
    
          public function offsetGet($offset) {
            return isset($this->container[$offset]) ? $this->container[$offset] : null;
          }
    
          function rewind() {
            $this->position = 0;
          }
    
          function current() {
            return $this->container[$this->position];
          }
    
          function key() {
            return $this->position;
          }
    
          function next() {
            ++$this->position;
          }
    
          function valid() {
            return isset($this->container[$this->position]);
          }
    }