📜 ⬆️ ⬇️

Iterator, ArrayAccess, Countable: Object as an array

0. Intro.


The standard php package has 2 interesting interfaces that allow 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 each, foreach, for. The second one, in turn, allows accessing an object as if using an array using the usual $ array [] = 'newItem'. Accordingly, in order to fully emulate the array, the object must implement both interfaces.

1. Iterator.


Iterator (aka Cursor) is a behavioral design pattern. In php is represented by the Iterator interface and requires the implementation of the following methods:

Accordingly, these methods are analogous to 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 possibility to change the values ​​that it contains.
')

2. ArrayAccess.


The implementation of this interface will allow to refer to the object as an array of any of the available functions. The interface contains 4 abstract methods:


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 as an array. But count () still returns 1 (why so? See http://www.php.net/manual/en/function.count.php ).

3. Countable.


The interface contains only one method that is created for use with count ().


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:


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 


The object now serializes only the data, not itself.

5. Results.


Combining the above classes 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 allows you to call an 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); 


Option backstop, 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 sampling 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 ).

Source: https://habr.com/ru/post/127711/


All Articles