Iterating objects using PHP and SPL
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 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