Iterator, ArrayAccess, Countable: Object as an array

    0. Intro.


    There are 2 interesting interfaces in the standard php package, allowing you to significantly change the behavior of objects in the language.
    These are Iterator and ArrayAccess. The first allows you to iterate the object through such constructions as each, foreach, for. The second, in turn, allows you to access the object as an array using the familiar $ array [] = 'newItem'. Accordingly, for full emulation of the array, the object must implement both interfaces.

    1. Iterator.


    Iterator (aka Cursor) is a behavioral design pattern. In php, it is represented by the Iterator interface and requires the implementation of the following methods:
    • public function rewind () - reset the pointer to the zero position;
    • public function current () - return of the current value;
    • public function key () - return the key of the current element;
    • public function next () - shift to the next element;
    • public function valid () - must be called after Iterator :: rewind () or Iterator :: next () to check if the current position is valid.

    Accordingly, these methods are analogs of the usual reset (), current (), key (), next ().

    Example 1:
    
    class Iteratable implements Iterator 
    {
        protected $_position = 0;
        protected $_container = array (
            'item1', 'item2', 'item3'
        );  
        public function __construct() 
        {
            $this->_position = 0;
        }
        public function rewind() 
        {
            $this->_position = 0;
        }
        public function current() 
        {
            return $this->_container[$this->_position];
        }
        public function key() 
        {
            return $this->_position;
        }
        public function next() 
        {
            ++$this->_position;
        }
        public function valid() 
        {
            return isset($this->_container[$this->_position]);
        }
    }
    $iteratable = new Iteratable;
    foreach ($iteratable as $item) {
        var_dump($iteratable->key(), $item);
    }
    


    But the current class is still not a pseudo-array. Now it still does not give the opportunity to change the values ​​that it contains.

    2. ArrayAccess.


    The implementation of this interface will allow you to access the object as an array of any of the available functions. The interface contains 4 abstract methods:
    • abstract public boolean offsetExists (mixed $ offset) - whether the value for the given key exists;
    • abstract public mixed offsetGet (mixed $ offset) - get the value by index;
    • abstract public void offsetSet (mixed $ offset, mixed $ value) - set the value with the index;
    • abstract public void offsetUnset (mixed $ offset) - delete the value.


    Example 2:
    
    class ArrayAccessable implements ArrayAccess 
    {
        protected $_container = array();
        public function __construct($array = null)
        {
            if (!is_null($array)) {
                $this->_container = $array;
            }
        }
        public function offsetExists($offset)
        {
            return isset($this->_container[$offset]);
        }
        public function offsetGet($offset)
        {
            return $this->offsetExists($offset) ? $this->_container[$offset] : null;
        }
        public function offsetSet($offset, $value)
        {
            if (is_null($offset)) {
                $this->_container[] = $value;
            } else {
                $this->_container[$offset] = $value;
            }
        }
        public function offsetUnset($offset)
        {
            unset($this->_container[$offset]);
        }
    }
    $array = new ArrayAccessable(array('a', 'b', 'c', 'd', 'e' => 'assoc'));
    var_dump($array);
    unset($array['e']);
    var_dump('unset: ', $array);
    $array['meta'] = 'additional element';
    var_dump('set: ', $array);
    var_dump(count($array));
    


    Now an instance of the ArrayAccessable class works like an array. But count () still returns 1 (why so? See http://www.php.net/manual/en/function.count.php ).

    3. Countable.


    The interface contains just one method, which is created for use with count ().
    • abstract public int count (void) - the number of elements of the object.


    Example 3
    
    class CountableObject implements Countable
    {
        protected $_container = array('a', 'b', 'c', 'd');
        public function count()
        {
            return count($this->_container);
        }
    }
    $countable = new CountableObject;
    var_dump(count($countable));
    

    But our object is still being serialized as an object, not an array ...

    4. Serializable.


    An interface that allows you to redefine how the object is serialized.
    Contains 2 methods with speaking names:
    • abstract public string serialize (void);
    • abstract public mixed unserialize (string $ serialized).


    Example 4
    
    class SerializableObject implements Serializable    
    {    
        protected $_container = array('a', 'b', 'c', 'd');    
        public function serialize()    
        {    
            return serialize($this->_container);    
        }    
        public function unserialize($data)    
        {    
            $this->_container = unserialize($data);    
        }    
    }    
    $serializable = new SerializableObject;    
    var_dump($serializable); // SerializableObject    
    file_put_contents('serialized.txt', serialize($serializable));    
    $unserialized = unserialize(file_get_contents('serialized.txt'));    
    var_dump($unserialized); // SerializableObject
    


    Now the object serializes only the data, not itself.

    5. Results.


    By combining the classes described above into one, we get an object that behaves like an array.
    The only drawback is that functions like array_pop () will not work with it.
    As a solution, you can use the new magic method from php 5.3 __invoke (), which will allow you to call the object as a function and thus make these functions work.
    
    public function __invoke(array $data = null)    
    {    
    	if (is_null($data)) {    
    		return $this->_container;    
    	} else {    
    		$this->_container = $data;    
    	}    
    }    
    $array = new SemiArray(array('a', 'b', 'c', 'd', 'e' => 'assoc'));    
    $tmp = $array();    
    array_pop($tmp);    
    $array($tmp);    
    var_dump('array_pop', $array);
    


    The option is backup, other options are waiting in your comments.
    Full listing of the resulting class:
    
    class SemiArray implements ArrayAccess, Countable, Iterator, Serializable    
    {    
        protected $_container = array();    
        protected $_position = 0;    
        public function __construct(array $array = null)    
        {    
            if (!is_null($array)) {    
                $this->_container = $array;    
            }    
        }    
        public function offsetExists($offset)    
        {    
            return isset($this->_container[$offset]);    
        }    
        public function offsetGet($offset)    
        {    
            return $this->offsetExists($offset) ? $this->_container[$offset] : null;    
        }    
        public function offsetSet($offset, $value)    
        {    
            if (is_null($offset)) {    
                $this->_container[] = $value;    
            } else {    
                $this->_container[$offset] = $value;    
            }    
        }    
        public function offsetUnset($offset)    
        {    
            unset($this->_container[$offset]);    
        }    
        public function rewind()    
        {    
            $this->_position = 0;    
        }    
        public function current()     
        {    
            return $this->_container[$this->_position];    
        }    
        public function key()     
        {    
            return $this->_position;    
        }    
        public function next()     
        {    
            ++$this->_position;    
        }    
        public function valid()     
        {    
            return isset($this->_container[$this->_position]);    
        }    
        public function count()    
        {    
            return count($this->_container);    
        }    
        public function serialize()    
        {    
            return serialize($this->_container);    
        }    
        public function unserialize($data)    
        {    
            $this->_container = unserialize($data);    
        }    
        public function __invoke(array $data = null)    
        {    
            if (is_null($data)) {    
                return $this->_container;    
            } else {    
                $this->_container = $data;    
            }    
        }    
    }
    

    Testing:
    
    $array = new SemiArray(array('a', 'b', 'c', 'd', 'e' => 'assoc'));
    var_dump($array);
    $array->next();
    var_dump('advanced key: ', $array->key());
    unset($array['e']);
    var_dump('unset: ', $array);
    $array['meta'] = 'additional element';
    var_dump('set: ', $array);
    echo 'count: ';
    var_dump(count($array));
    file_put_contents('serialized.txt', serialize($array));
    echo 'unserialized:';
    var_dump($array); // SemiArray
    $array = unserialize(file_get_contents('serialized.txt'));
    $tmp = $array();
    array_pop($tmp);
    $array($tmp);
    var_dump('array_pop', $array);
    

    6. Scope.


    6.1. For example, in the results of a selection from the database.

    Warning! Pseudocode
    
    class Rowset extends SemiArray    
    {    
        protected $_total;    
        public function __construct()    
        {    
            $this->_total = $db->query('SELECT FOUND_ROWS() as total')->total;    
        }    
        public function getTotal()    
        {    
            return $this->_total;    
        }    
    }    
    $rowset = new Rowset();    
    while ($obj = mysql_fetch_object($res)) {    
        $rowset[] = $obj;    
    }    
    foreach ($rowset as $row) {    
        // ...    
    }    
    $rowset->getTotal(); // total row count
    


    7. Outro.


    The article was written for educational purposes, and the implementation is already available in php in the build-in class ArrayObject ( http://www.php.net/manual/en/class.arrayobject.php ).

    Also popular now: