Rendere un oggetto iterabile
Dalla versione 5.0 PHP ha integrato all'interno della propria distribuzione standard la componente Standard PHP Library (SPL) che fornisce una serie di interfacce e classi per risolvere problemi comuni. Questa scelta è nata con la volontà di migliorare il supporto per la programmazione orientata agli oggetti nel linguaggio.
Tra le interfacce disponibili troviamo Iterator
e IteratorAggregate
che permettono di rendere un oggetto compatibile con il costrutto foreach
come se si trattasse di un array mantenendo però l'essenza di oggetto che può integrare la propria logica business. Questa tecnica viene comunemente utilizzata dagli ORM (framework per l'accesso ai database) per eseguire un ciclo foreach
su un set di risultati continuando ad aver accesso a metodi specifici.
Il codice mostra come iterare su un array presente in una classe che ha rapporti con diverse istanze di un'altra classe.
<?php
class MyManager
{
private $objects;
public function __construct()
{
$this->objects = array();
}
public function add(MyClass $object)
{
$this->objects[] = $object;
}
public function getObjects()
{
return $this->objects;
}
}
$manager = new MyManager();
$manager->add(new MyClass(5));
$manager->add(new MyClass(10));
$manager->add(new MyClass(2));
$objects = $manager->getObjects();
foreach ($objects as $object) {
echo $object->getValue();
}
// Output: 5, 10, 2
Implementare l'interfaccia Iterator
In questo caso però stiamo dando anche accesso in scrittura all'array presente nella classe e in generale si tratta di una soluzione poco elegante, ma possiamo implementare l'interfaccia Iterator
aggiungendo 5 metodi (tutti public e senza parametri) alla nostra classe oltre ad una variabile per tenere traccia della posizione attuale durante l'iterazione.
Metodo | Descrizione |
---|---|
key() |
Restituisce l'indice attuale del cursore (cioè la variabile menzionata precedentemente). |
current() |
Restituisce l'oggetto alla posizione attuale del cursore. |
next() |
Sposta il cursore alla posizione successiva e non restituisce niente. |
rewind() |
Sposta il cursore alla posizione iniziale e non restituisce niente. |
valid() |
Restituisce un valore booleano true se la posizione attuale del cursore corrisponde ad un oggetto, altrimenti false . Nel caso in cui venga restituito false il ciclo viene terminato. |
Durante un ciclo foreach
viene richiamato il metodo rewind()
portando il cursore alla posizione iniziale, quindi viene chiamato il metodo valid()
per controllare se la posizione attuale è disponibile, in caso positivo vengono chiamati i metodi key()
e current()
per ottenere l'indice e il valore attuali. A questo punto viene chiamato il metodo next()
e il ciclo ricomincia dal metodo valid()
fino a che non si ottiene un valore false
.
Leggendo attentamente l'ordine delle chiamate ci si rende conto che alla fine di un ciclo foreach
il cursore interno è spostato all'ultima posizione e non viene reimpostato sullo stato iniziale. Un esempio concreto di classe che implementa Iterator
è il seguente:
<?php
class MyManager implements Iterator
{
private $objects;
private $position = 0;
public function __construct()
{
$this->objects = array();
}
public function add(MyClass $object)
{
$this->objects[] = $object;
}
public function key()
{
return $this->position;
}
public function current()
{
return $this->objects[$this->position];
}
public function next()
{
$this->position += 1;
}
public function rewind()
{
$this->position = 0;
}
public function key()
{
return $this->position;
}
public function valid()
{
return isset($this->objects[$this->position]) || array_key_exists($this->position, $this->objects);
}
}
$manager = new MyManager();
$manager->add(new MyClass(5));
$manager->add(new MyClass(10));
$manager->add(new MyClass(2));
foreach ($manager as $object) {
echo $object->getValue();
}
// Output: 5, 10, 2
Semplificare il costrutto con l'interfaccia IteratorAggregate
Benché sia semplice implementare l'interfaccia Iterator, questa caratteristica ha un utilizzo prevalentemente in situazioni standard come l'iterazione su un array interno. Per evitare di reinventare la ruota è possibile sfruttare un'altra interfaccia, IteratorAggregate
, insieme alle classi di PHP SPL.
L'interfaccia IteratorAggregate
espone un singolo metodo, getIterator()
, da richiamare senza parametri che restituisce un oggetto che implementa l'interfaccia Traversable
(l'interfaccia che viene estesa da Iterator
). Quindi l'esempio precedente si semplifica così:
<?php
class MyManager implements IteratorAggregate
{
private $objects;
public function __construct()
{
$this->objects = array();
}
public function add(MyClass $object)
{
$this->objects[] = $object;
}
public function getIterator()
{
return new ArrayIterator($this->objects);
}
}
$manager = new MyManager();
$manager->add(new MyClass(5));
$manager->add(new MyClass(10));
$manager->add(new MyClass(2));
foreach ($manager as $object) {
echo $object->getValue();
}
// Output: 5, 10, 2