Iterarea obiectelor folosind PHP si SPL

Read this post in English

Share on:

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:

iterator diagram


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