Iterating objects using PHP and SPL

Citește postarea în română

Share on:

Iterator patter is probably the most popular pattern from SPL. Is a very simple way to demonstrate the advantages of an interface and SPL.

Motivation:
The possibility of iterating object type structures, using functions like foreach(), var_dump(), print_r() etc.

Diagram:

iterator diagram


Iterator structure:

In SPL there are a lot of interfaces and classes for iteration.

Iterator interface base structure:

 1/**
 2 * Iterator interface from SPL
 3 */
 4Iterator extends Traversable {
 5      /**
 6       * Returns the current element
 7       */
 8      abstract public mixed current ( void )
 9
10      /**
11       * Returns the key of the current element
12       */
13      abstract public scalar key ( void )
14
15      /**
16       * Moves to the next element in the array
17       */
18      abstract public void next ( void )
19
20      /**
21       * Reset the iteration to the initial position
22       */
23      abstract public void rewind ( void )
24
25      /**
26       * Check to see if the current position is valid
27       */
28      abstract public boolean valid ( void )
29}

Example 1:

A simple iterator object.

 1/**
 2 * The class for the iterator object
 3 */
 4class Iterabil implements Iterator {
 5
 6	/**
 7	 * The index for the iterated element
 8	 */
 9	private $_current = 0;
10
11	/**
12	 * Array with elements to iterate
13	 */
14	private $_elements = array();
15
16	/**
17	 * Constructor
18	 *
19	 * @param array $elements Elements to iterate
20	 */
21	public function __construct($elements) {
22		$this->_elements = $elements;
23	}
24
25	/**
26	 * Current element
27	 *
28	 * @return mixed Current element
29	 */
30	public function current() {
31		return $this->_elements[$this->_current];
32	}
33
34	/**
35	 * Current index
36	 *
37	 * @return integer Current index
38	 */
39	public function key() {
40		return $this->_current;
41	}
42
43	/**
44	 * Move to the next index
45	 */
46	public function next() {
47		$this->_current++;
48	}
49
50	/**
51	 * Reset index
52	 */
53	public function rewind() {
54		$this->_current = 0;
55	}
56
57	/**
58	 * Check if the current element is set
59	 *
60	 * @return boolean If the current element is set
61	 */
62	public function valid() {
63          return isset($this->_elements[$this->_current]);
64     }
65}
66
67// class instance
68$obj = new Iterabil(array(1, 2, 3, 4, 5));
69
70// iterate object
71foreach ($obj as $value) {
72     echo $value.PHP_EOL;
73}
74
75// output:
76// 1
77// 2
78// 3
79// 4
80// 5

Example 2:

Another example a little more complex, a class that allows to iterate through the public properties of a class which extends it. Iterator and Reflection are used.

 1/**
 2 * Class which iterates through the public properties of a class which extends it
 3 */
 4class Iterabil implements Iterator {
 5
 6     /**
 7       * The index for the iterated element
 8       */
 9     private $_current = 0;
10
11     /**
12      * Array with elements to iterate
13      */
14     private $_elements = array();
15
16     /**
17      * Current element
18      *
19      * @return mixed Current element
20      */
21     public function current() {
22          return $this->_elements[$this->_current]->name;
23     }
24
25     /**
26      * Current index
27      *
28      * @return integer Current index
29      */
30     public function key() {
31          return $this->_current;
32     }
33
34     /**
35      * Move to next index
36      */
37     public function next() {
38          $this->_current++;
39     }
40
41     /**
42      * Reset index and get the properties
43      */
44     public function rewind() {
45          // rewind is the first to be called
46          // here the properties list should be obtained
47          // ReflectionClass is initialized
48          // with the current class name as a parameter
49          $reflection = new ReflectionClass(get_class($this));
50
51          // we get the public properties
52          $this->_elements = $reflection->getProperties(ReflectionMethod::IS_PUBLIC);
53
54          // set the current index
55          $this->_current = 0;
56     }
57
58     /**
59      * Check if the current element is set
60      *
61      * @return boolean If the current element is set
62      */
63     public function valid() {
64         return isset($this->_elements[$this->_current]);
65    }
66}
67
68/**
69 * A new class with public properties
70 *
71 */
72class Testing extends Iterabil {
73     public $proprietate1;
74     public $proprietate2;
75}
76
77// class instance
78$obj = new Testing();
79
80// iterate object
81foreach ($obj as $value) {
82     echo $value.PHP_EOL;
83}
84
85// output:
86// proprietate1
87// proprietate2

And if you what the above example to be accessible as an array you just have to implement ArrayAccess from SPL.

ArrayAccess structure:

 1ArrayAccess   {
 2    /**
 3     * Check if the offset exists
 4     */
 5    abstract public boolean offsetExists ( string $offset );
 6
 7    /**
 8     * Returns the element of an offset or NULL if it does not exist
 9     */
10    abstract public mixed offsetGet ( string $offset );
11
12    /**
13     * Set a value for an offset
14     */
15    abstract public void offsetSet ( string $offset , string $value );
16
17    /**
18     * Unset a value for an offset
19     */
20    abstract public void offsetUnset ( string $offset )
21}

Example 3:

An even more complicated example which shows the power of interfaces from SPL. Iterator object accessible like an array.

To simplify the array access logic I’ve used the php native functions for iterating an array (next(), reset()).

  1/**
  2 * The class for the iterator object
  3 */
  4class Iterabil implements Iterator, ArrayAccess, Countable {
  5
  6     /**
  7      * Array with elements to iterate
  8      */
  9     private $_elements = array();
 10
 11     /**
 12      * Constructor
 13      *
 14      * @param array $elements Elementele de iterat
 15      */
 16     public function __construct($elements) {
 17          $this->_elements = $elements;
 18     }
 19
 20     /**
 21      * Current element
 22      *
 23      * @return mixed Current element
 24      */
 25     public function current() {
 26          return current($this->_elements);
 27     }
 28
 29     /**
 30      * Current index
 31      *
 32      * @return integer Current index
 33      */
 34     public function key() {
 35          return key($this->_elements);
 36     }
 37
 38     /**
 39      * Move to the next index
 40      */
 41     public function next() {
 42          next($this->_elements);
 43     }
 44
 45     /**
 46      * Reset index
 47      */
 48     public function rewind() {
 49          reset($this->_elements);
 50     }
 51
 52     /**
 53      * Check if the current element is set
 54      *
 55      * @return boolean If the current element is set
 56      */
 57     public function valid() {
 58          return current($this->_elements)?true:false;
 59    }
 60    /**
 61     * Check if the offset exists
 62     *
 63     * @param string $offset Element key
 64     * @return boolean If the element is set
 65     */
 66    public function offsetExists($offset) {
 67         return isset($this->_elements[$offset]);
 68    }
 69
 70    /**
 71     * Returns the element of an offset or NULL if it does not exist
 72     *
 73     * @param string $offset Array offset
 74     * @return mixed Element or NULL
 75     */
 76    public function offsetGet($offset) {
 77         return $this->_elements[$offset];
 78    }
 79
 80    /**
 81     * Set a value for an offset
 82     *
 83     * @param string $offset Element offset
 84     * @param mixed $value Value of the element in the array
 85     */
 86    public function offsetSet($offset, $value) {
 87         $this->_elements[$offset] = $value;
 88    }
 89
 90    /**
 91     * Unset a value for an offset
 92     *
 93     * @param string $offset Element offset
 94     */
 95    public function offsetUnset($offset) {
 96         unset($this->_elements[$offset]);
 97    }
 98
 99    /**
100     * Number of elements in the array
101     *
102     * @return integer Number of elements in array
103     */
104    public function count() {
105         return count($this->_elements);
106    }
107}
108
109// Class instance
110$obj = new Iterabil(array(1, 2, 3, 4, 5));
111
112echo 'Iteration using "for":'.PHP_EOL;
113
114// iterate the object like a simple array
115for($i = 0; $i < count($obj); $i++) {
116     echo $obj[$i].PHP_EOL;
117}
118
119echo 'Element to delete: '.$obj[1].PHP_EOL;
120
121unset($obj[1]);
122
123echo 'Iteration using "foreach":'.PHP_EOL;
124
125// iterate the object using foreach
126foreach ($obj as $element) {
127     echo $element.PHP_EOL;
128}
129
130// Output:
131//Iteration using "for":
132//1
133//2
134//3
135//4
136//5
137//Element to delete: 2
138//Iteration using "foreach":
139//1
140//3
141//4
142//5