Iterarea obiectelor folosind PHP si SPL
Iterator pattern este probabil cel mai popular pattern din SPL. Este un exemplu foarte simplu de a demonstra avantajele unei interfete si SPL.
Motivatie:
Posibilitatea de a itera structuri de tip obiect folosind functii precum foreach(), var_dump(), print_r() etc.
Diagrama:
Structura Iterator:
In SPL se gasesc mai multe interfete si clase pentru iterator.
Structura de baza pentru interfata Iterator:
1/**
2 * Interfata Iterator din SPL
3 */
4Iterator extends Traversable {
5 /**
6 * Intoarce elementul curent
7 */
8 abstract public mixed current ( void )
9
10 /**
11 * Cheia pentru elementul curent
12 */
13 abstract public scalar key ( void )
14
15 /**
16 * Trece la urmatorul element
17 */
18 abstract public void next ( void )
19
20 /**
21 * Reseteaza iterarea la pozitia initiala
22 */
23 abstract public void rewind ( void )
24
25 /**
26 * Verifica daca pozitia curenta este valida
27 */
28 abstract public boolean valid ( void )
29}
Exemplu 1:
Un obiect iterabil simplu.
1/**
2 * Clasa care va genera un obiect interabil
3 */
4class Iterabil implements Iterator {
5
6 /**
7 * Index pentru elementul iterabil
8 */
9 private $_current = 0;
10
11 /**
12 * Array cu elementele de iterat
13 */
14 private $_elements = array();
15
16 /**
17 * Constructor
18 *
19 * @param array $elements Elementele de iterat
20 */
21 public function __construct($elements) {
22 $this->_elements = $elements;
23 }
24
25 /**
26 * Elementul curent
27 *
28 * @return mixed Elementul curent
29 */
30 public function current() {
31 return $this->_elements[$this->_current];
32 }
33
34 /**
35 * Index curent
36 *
37 * @return integer Index curent
38 */
39 public function key() {
40 return $this->_current;
41 }
42
43 /**
44 * Trecerea la index-ul urmator
45 */
46 public function next() {
47 $this->_current++;
48 }
49
50 /**
51 * Resetare index
52 */
53 public function rewind() {
54 $this->_current = 0;
55 }
56
57 /**
58 * Verifica daca elementul curent este setat
59 *
60 * @return boolean Daca elementu curent este setat
61 */
62 public function valid() {
63 return isset($this->_elements[$this->_current]);
64 }
65}
66
67// instantiere clasa
68$obj = new Iterabil(array(1, 2, 3, 4, 5));
69
70// iterare obiect
71foreach ($obj as $value) {
72 echo $value.PHP_EOL;
73}
74
75// output:
76// 1
77// 2
78// 3
79// 4
80// 5
Exemplu 2:
Un exemplu putin mai complex, o clasa care permite iterarea prin proprietatile publice ale clasei care o extinde. Se folosesc Iterator si Reflection.
1/**
2 * Clasa care va genera un obiect cu proprietatile publice iterabile
3 */
4class Iterabil implements Iterator {
5
6 /**
7 * Index pentru elementul iterabil
8 */
9 private $_current = 0;
10
11 /**
12 * Array cu elementele de iterat
13 */
14 private $_elements = array();
15
16 /**
17 * Elementul curent
18 *
19 * @return mixed Proprietatea curenta in iteratie
20 */
21 public function current() {
22 return $this->_elements[$this->_current]->name;
23 }
24
25 /**
26 * Index curent
27 *
28 * @return integer Index curent
29 */
30 public function key() {
31 return $this->_current;
32 }
33
34 /**
35 * Trecerea la index-ul urmator
36 */
37 public function next() {
38 $this->_current++;
39 }
40
41 /**
42 * Resetare index si preluare prorietari
43 */
44 public function rewind() {
45 // rewind se apeleaza prima,
46 // deci aici ar trebui preluate prorprietatile obiectului
47 // se initializeaza obiectul de tip ReflectionClass
48 // cu parametru numele clasei curente
49 $reflection = new ReflectionClass(get_class($this));
50
51 // se extrag metodele publice
52 $this->_elements = $reflection->getProperties(ReflectionMethod::IS_PUBLIC);
53
54 // se seteaza indexul curent
55 $this->_current = 0;
56 }
57
58 /**
59 * Verifica daca elementul curent este setat
60 *
61 * @return boolean Daca elementu curent este setat
62 */
63 public function valid() {
64 return isset($this->_elements[$this->_current]);
65 }
66}
67
68/**
69 * O noua clasa care va avea proprietati publice
70 *
71 */
72class Testing extends Iterabil {
73 public $proprietate1;
74 public $proprietate2;
75}
76
77// instantiere clasa
78$obj = new Testing();
79
80// iterare obiect
81foreach ($obj as $value) {
82 echo $value.PHP_EOL;
83}
84
85// output:
86// proprietate1
87// proprietate2
Iar daca vrem ca exemplu de mai sus sa fie si accesibil ca un array, nu trebuie decat sa mai implementam din SPL ArrayAccess.
Structura ArrayAccess:
1ArrayAccess {
2 /**
3 * Verifica daca un offset exista
4 */
5 abstract public boolean offsetExists ( string $offset );
6
7 /**
8 * Intoarce elementul unui offset sau NULL daca nu exista
9 */
10 abstract public mixed offsetGet ( string $offset );
11
12 /**
13 * Seteaza o valoare pentru un offset
14 */
15 abstract public void offsetSet ( string $offset , string $value );
16
17 /**
18 * Dezaloca o valoare pentru un offset
19 */
20 abstract public void offsetUnset ( string $offset )
21}
Exemplu 3:
Un exemplu destul de dificil care are rolul de a evidentia puterea interfetelor din SPL. Obiect iterabil si accesibil ca un array.
Pentru a simplifica logica de acces am folosit chiar functiile pentru parcurgerea unui array (next(), reset()).
1/**
2 * Clasa care va genera un obiect interabil
3 */
4class Iterabil implements Iterator, ArrayAccess, Countable {
5
6 /**
7 * Array cu elementele de iterat
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 * Elementul curent
22 *
23 * @return mixed Elementul curent
24 */
25 public function current() {
26 return current($this->_elements);
27 }
28
29 /**
30 * Index curent
31 *
32 * @return integer Index curent
33 */
34 public function key() {
35 return key($this->_elements);
36 }
37
38 /**
39 * Trecerea la index-ul urmator
40 */
41 public function next() {
42 next($this->_elements);
43 }
44
45 /**
46 * Resetare index
47 */
48 public function rewind() {
49 reset($this->_elements);
50 }
51
52 /**
53 * Verifica daca elementul curent este setat
54 *
55 * @return boolean Daca elementu curent este setat
56 */
57 public function valid() {
58 return current($this->_elements)?true:false;
59 }
60 /**
61 * Verifica daca un offset exista
62 *
63 * @param string $offset Element cautat
64 * @return boolean Daca elementul de la offset exista
65 */
66 public function offsetExists($offset) {
67 return isset($this->_elements[$offset]);
68 }
69
70 /**
71 * Elementul de la un offset
72 *
73 * @param string $offset Offset array
74 * @return mixed
75 */
76 public function offsetGet($offset) {
77 return $this->_elements[$offset];
78 }
79
80 /**
81 * Setare element in array
82 *
83 * @param string $offset Offset element
84 * @param mixed $value Valoarea pentru elementul din array
85 */
86 public function offsetSet($offset, $value) {
87 $this->_elements[$offset] = $value;
88 }
89
90 /**
91 * Dezalocare element in array
92 *
93 * @param string $offset Offset element
94 */
95 public function offsetUnset($offset) {
96 unset($this->_elements[$offset]);
97 }
98
99 /**
100 * Numarul de elemente din array-ul curent
101 *
102 * @return integer Nr elemente array
103 */
104 public function count() {
105 return count($this->_elements);
106 }
107}
108
109// instantiere clasa
110$obj = new Iterabil(array(1, 2, 3, 4, 5));
111
112echo 'Iterare folosind "for":'.PHP_EOL;
113
114// iterare ca printr-un array simplu
115for($i = 0; $i < count($obj); $i++) {
116 echo $obj[$i].PHP_EOL;
117}
118
119echo 'Element de sters: '.$obj[1].PHP_EOL;
120
121unset($obj[1]);
122
123echo 'Iterare folosind "foreach":'.PHP_EOL;
124
125// iterare array folosind foreach
126foreach ($obj as $element) {
127 echo $element.PHP_EOL;
128}
129
130// Output:
131//Iterare folosind "for":
132//1
133//2
134//3
135//4
136//5
137//Element de sters: 2
138//Iterare folosind "foreach":
139//1
140//3
141//4
142//5