Con l'uscita di Php 5 e le notevoli migliorie apportate al modello ad oggetti è cresciuta sempre di più la necessità di avere a disposizione degli oggetti e strutture di dati che permettessero un approccio più completo e diretto verso la programmazione ad oggetti con un linguaggio che fino ad ora era stato per lo più funzionale. I designer e gli sviluppatori di Php hanno deciso di aggiungere alla lista di funzioni compilate con la distribuzione ufficiale di Php un insieme di classi per migliorare l'approccio alla programmazione ad oggetti e potenziarne le funzionalità; questo insieme di classi è stato raggruppato all'interno della Standard Php Library (a cui d'ora in poi faremo riferimento utilizzando l'acronimo SPL) che si sta arricchendo di funzionalità a mano a mano che le versioni di Php avanzano.
In questo articolo introdurrò alcune delle classi più utili della SPL, fornendo qualche esempio pratico sul loro utilizzo. Approfondiremo le altre classi in altri articoli cercando di fornire con il tempo una panoramica più completa a questa interessante soluzione.
Struttura della libreria
Prima di affrontare qualunque esempio pratico è buona norma introdurre l'argomento che si andrà a trattare in modo complessivo. La SPL è una serie di interfacce e classi native che sono state sviluppate con lo scopo di permettere la risoluzione delle problematiche di programmazione comuni e l'implementazione di classi per l'accesso efficiente ai dati. Possiamo raggruppare le funzionalità esposte in sette gruppi principali:
-
Iteratori: la libreria SPL offre interfacce ed implementazioni di molti algoritmi per l'iterazione sugli oggetti. Un iteratore è un oggetto che permette di attraversare i singoli elementi che compongono una collezione a cui fa riferimento. Gli iteratori, che discuterò in dettagli in seguito con degli esempi, sono molto interessanti anche perchè permettono agli oggetti di comportarsi come se fossero array nel momento in cui vengono ciclati utilizzando il costrutto foreach;
-
Directory e File: una serie di classi ed interfacce per la gestione avanzata di file e directory;
-
XML: per ora viene fornito solamente un iteratore che permette di ciclare su un oggetto SimpleXML come se fosse un array;
-
Array Overloading: insieme di interfacce molto interessanti che rendono gli oggetti che le implementano capaci di comportarsi come se fossero array, con la possibilità di accedere agli elementi utilizzando l'operatore [];
-
Counting: espone una singola interfaccia che permette di rendere un oggetto contabile utilizzando la funzione count;
-
Eccezioni: una serie di eccezioni utilizzate dalla SPL ed utilizzabili anche all'interno dei propri script per gestire situazioni anomali comuni anzichè appoggiarsi alla gestione degli errori standard di Php;
-
Observer: interfacce che permettono l'implementazione del pattern observer in Php;
Ognuna delle classi o interfacce esposte risulta molto utile poichè l'engine di Php 5 è stato sviluppato affichè possa gestire in modo specifico oggetti che estendono determinate interfacce, rendendo così il linguaggio un po' più duttile e meno statico a livello strutturale.
Array overloading
Sicuramente l'overload degli oggetti per permettergli di comportarsi come se fossero array non è una delle funzionalità più importanti della SPL, ma a mio parere permette delle soluzioni implementative molto interessanti e per questo mi appresto a descriverla per prima. Questo gruppo espone fondamentalmente tre classi: ArrayObject
, ArrayIterator
e RecursiveArrayIterator
. Le ultime due sono semplicemente delle implementazioni specifici di iteratori che permettono di effettuare cicli foreach su un ArrayObject, mentre la prima classe è quella più interessante:
<?php class RangeObject extends ArrayObject { private $min; private $max; private $step; public function __construct($min, $max, $step=1) { parent::__construct(); $this->min = $min; $this->max = $max; $this->step = $step; } public function append($value) { throw Exception('Non possono essere aggiunti valori ad un RangeObject'); } public function offsetExists($offset) { $o = $offset * $this->step; return $o <= $this->max and $o >= $this->min; } public function offsetGet($offset) { return $offset >= count($this) ? null : ($offset * $this->step) + $this->min; } public function offsetSet($offset, $value) { throw Exception('Non possono essere assegnati valori ad un RangeObject'); } public function offsetUnset($offset) { throw Exception('Non possono essere rimossi valori da un RangeObject'); } public function count() { return floor(($this->max - $this->min) / $this->step) + 1; } } $obj = new RangeObject(2, 23, 3); echo "Il quarto valore è: ".$obj[3]."<br />"; echo "Il settimo valore è: ".$obj[6]."<br />"; echo "Numero di elementi: ".count($obj)."<br />"; ?>
L'implementazione non pretende di essere utile, ma permette di creare un oggetto che si comporta come un array restituito dalla funzione range con la differenza che gli elementi vengono generati solamente quando richiesti, sia direttamente usando l'operatore [] che automaticamente all'interno di un ciclo foreach quando avremo aggiunto il supporto agli iteratori.
Le funzioni fondamentali da implementare sono le seguenti:
-
append: aggiunge un elemento all'array;
-
count: restituisce il numero degli elementi dell'array;
-
offsetExists: controlla se l'offset specificato contiene un valore;
-
offsetSet/offsetGet: recuperano ed assegnano un valore ad un determinato offset;
-
offsetUnset: elimina il valore associato ad un determinato offset;
Ovviamente il parametro offset può essere sia una stringa che un numero, quindi possiamo emulare anche il comportamento di tabelle hash.
Un breve sguardo agli iteratori
L'iteratore è un oggetto che permette di ciclare sui singoli elementi di una collezione a cui questo fa riferimento. Vediamo prima un'implementazione che aggiunge all'ArrayObject precedente il supporto all'iterazione, poi discuteremo brevemente il funzionamento e l'utilità:
<?php
class RangeIterator extends ArrayIterator
{
private $obj;
private $i;
public function __construct(RangeObject $obj)
{
$this->obj = $obj;
$this->i = 0;
}
public function current()
{
return $this->obj[$this->i];
}
public function key()
{
return $this->i;
}
public function next()
{
$this->i += 1;
}
public function rewind()
{
$this->i = 0;
}
public function seek($pos)
{
$this->i = $pos;
}
public function valid()
{
return $this->i < count($this->obj);
}
}
class IterableRangeObject extends RangeObject
{
public function getIterator()
{
return new RangeIterator($this);
}
}
$obj = new IterableRangeObject(2, 23, 3);
foreach($obj as $key => $value)
{
echo $key," => ",$value,"<br/>";
}
?>
Con poche righe di codice abbiamo ottenuto un oggetto che emula un array e che può essere ciclato usando il costrutto foreach. Anche se non sembra abbiamo ottenuto un enorme vantaggio: possiamo permetterci di iterare su un range immenso senza doverci preoccupare della memoria occupata dato che i valori vengono generati solamente quando richiesti mentre l'utilizzo della funzione range avrebbe creato un array in memoria delle dimensioni necessarie. Usando approcci simili è possibile anche creare iteratori che operino su oggetti con un numero infinito o imprecisato di elementi. Se questo probabilmente non accade spesso nello sviluppo Web, in determinate applicazioni desktop potrebbe risultare molto comodo.
In questa implementazione specifica abbiamo implementato ArrayIterator
anche se il pattern standard per lo sviluppo degli iteratori in Php prevede l'implementazione dell'interfaccia Iterator
per l'iteratore e di IteratorAggregate
per l'oggetto che deve essere iterabile. In questo caso ArrayObject
implementa già l'interfaccia richiesta. Un esempio:
<?php
class MyIterator implements Iterator
{
// ....
}
class MyObject implements IteratorAggragate
{
// .....
public function getIterator()
{
return new MyIterator($this);
}
}
?>
La SPL espone un sacco di interfacce per l'implementazione degli iteratori che permettono l'iterazione ricorsiva, l'iterazioni su Directory e l'iterazione con caching degli elementi. Comunque, tutte queste interfacce presuppongono che l'implementazione definisca i seguenti metodi:
-
current: restituisce il valore corrente dell'iteratore;
-
key: restituisce la chiave associata al valore corrente;
-
next: avanza l'iteratore di una posizione;
-
rewind: ritorna l'iteratore allo stato iniziale;
-
seek: muove il puntatore interno dell'iteratore ad una determinata posizione;
-
valid: controlla se l'elemento che si è raggiunto con next è valido;
Le funzioni omonime ai metodi sopra definiti possono essere utilizzate per operare su un iteratore come se questi fosse un array.
Conclusioni
Lo spazio è terminato e le cose da dire sono ancora moltissime. Nei prossimi articoli cercherò di trattare in modo più approfondito i vari tipi di iteratori implementabili, fornendo esempi pratici di utilizzo. Comprendere il funzionamento e l'utilità della SPL è un primo passo verso l'acquisizione della capacità di produrre codice più completo e manutenibile, nonchè un modo per sfruttare implementazioni native molto veloci di soluzioni che in caso di implementazione manuale senza appoggio dell'engine risulterebbero sicuramente più lente e macchinose. A settimana prossima.