I generators, già disponibili in altri linguaggi di sviluppo come per esempio Python, sono una delle novità più interessanti tra quelle introdotte con la versione 5.5 di PHP; essi rappresentano una soluzione per implementare espressioni finalizzate all’iterazione di oggetti (iterators), senza generare overhead, cioè il ricorso a risorse accessorie, o la necessità di dover ricorrere a classi che implementino l’interfaccia predefinita di PHP per l’iterazione (Iterator interface).
Facendo ricorso ai generators sarà possibile digitare codici sorgenti meno articolati rispetto a quelli previsti per l’implementazioni di iterators e, nel contempo, sviluppare applicazioni più performanti in fase di esecuzione; ciò è dovuto al fatto che attraverso di essi, l’utilizzo del ciclo foreach a carico di un dataset non richiederà l’archiviazione di un array, in questo modo non si avranno allocazioni in memoria tali da eccedere il limite massimo consentito e i tempi richiesti per le esecuzioni risulteranno più rapidi.
Tramite i generators si potranno definire funzioni in grado di operare come qualsiasi altro costrutto dello stesso tipo ma nello specifico, invece di effettuare un’unica restituzione, con esse si avranno tante ripetizioni quante saranno quelle richieste per esaurire il totale delle iterazioni previste.
In questa breve trattazione verrà presentata la sintassi richiesta per l’utilizzo dei generators e presentati alcuni esempi pratici del loro impiego.
Sintassi dei generators: la keyword yield
Per quanto riguarda la meccanica relativa al funzionamento dei generators, è necessario sottolineare che questi non sono stati concepiti per la restituzione di valori di ritorno, anzi, un’eventualità del genere darebbe luogo alla notifica di un errore da parte del parser di PHP, sono invece consentiti return privi di valori per la terminazione dei generators stessi.
Quando si invoca una funzione basata sui generators, essa ha il compito di rendere disponibile un oggetto destinato all’iterazione; il ciclo porterà ad una chiamata della funzione che si ripeterà tutte le volte che sarà richiesto un valore e l’ultimo stato del generator verrà registrato. Tale salvataggio consentirà poi di riattivare il generator al momento della richiesta del valore successivo.
La procedura descritta non si ripeterà naturalmente all’infinito, ma terrà conto del numero di iterazioni previste, una volta esaurito il compito del generator l’esecuzione proseguirà con le restanti istruzioni contenute nel sorgente.
Alla base della sintassi dei generators vi è una parola chiave, yield
, essenzialmente un’espressione fondata su di essa potrebbe ricordare un’istruzione basata su return
(come nelle comuni funzioni definite dagli utenti); ma yield non ha il compito di arrestare l’esecuzione di una funzione per la restituzione del relativo valore di ritorno, essa infatti dovrà fornire un valore al ciclo di iterazione e mettere in pausa l’esecuzione della funzione senza terminare il generator associato.
Un esempio elementare di utilizzo della keyword yield nei generators potrebbe essere il seguente:
<?php
/*
definizione e utilizzo di una funzione
basata su un generator
*/
// funzione del generator
function semplice_ciclo() {
// intervallo di valori
for ($x = 5; $x <= 10; $x++) {
// conservazione dei valori per l'interazione
yield $x;
}
}
// memorizzazione del generator in una variabile
$iterazioni = semplice_ciclo();
// ciclo del dataset fornito dal generator
foreach ($iterazioni as $iterazione) {
echo $iterazione."n";
}
?>
La funzione proposta prevede di stampare a video tutte le cifre comprese nell’intervallo tra “5” e “10” prodotte tramite un ciclo for con incremento unitario; tutti i valori che soddisfano la condizione stabilita verranno quindi “affidati” a yield in modo che diventino disponibili nel momento in cui vi sarà la chiamata alla funzione; il generator permetterà di ciclare i valori messi a disposizione dall’istruzione associata alla keyword fino all’ultima iterazione prevista. Il tutto senza allocare alcun vettore in memoria, ma con un meccanismo simile a quello degli array non associativi; infatti il generator si occuperà di definire automaticamente la sequenza di chiavi (sotto forma di numeri interi) relativa ai valori da ciclare.
Da notare che la sintassi dei generators prevede che, nel caso in cui si voglia allocare l’esito di un’istruzione yield in una variabile, sarà necessaria una sua delimitazione tra parentesi tonde, per cui non si potrà utilizzare un formato del genere:
$variabile = yield $valore;
mentre la forma corretta sarà:
$variabile = (yield $valore);
Definire coppie chiave/valore tramite i generators
Come anticipato, i generators supportano l’assegnazione automatica di chiavi ai valori da iterare, ma si potrà utilizzare anche un meccanismo mutuato da quello previsto per gli array associativi; a questo proposito è possibile analizzare il semplice esempio riportato di seguito:
<?php
/*
definizione di coppie chiave/valore nei generators
*/
// stringa per la suddivisione
$stringa = <<<'EOF'
a,Pippo,cane parlante
b,Pluto,cane non parlante
c,Paperino,pennuto parlante
EOF;
// funzione del generator
function get_key($stringa) {
// suddivisione della stringa in righe
foreach (explode("n", $stringa) as $riga) {
// suddivisione delle righe in sottostringhe
$records = explode(',', $riga);
// estrazione del primo elemento di ogni riga
$chiave = array_shift($records);
// utilizzo degli elementi estratti come identificatori
yield $chiave => $records;
}
}
// ciclo del dataset
foreach (get_key($stringa) as $chiave => $records) {
echo $chiave. " - " .$records[0]. " " .$records[1] . "<br/ >n";
}
?>
Il codice presentato contiene innanzitutto una stringa, introdotta tramite sintassi Nowdoc, che è suddivisa in tre righe, ogni riga contiene a sua volta tre sottostringhe separate da virgole; l’applicazione non farà altro che isolare il primo elemento/valore di ciascuna sottostringa in modo da utilizzarlo come chiave, gli altri due elementi di ogni sottostringa saranno associati a chiavi numeriche sequenziali assegnate automaticamente.
Anche in questo caso è da tenere presente che dovranno essere utilizzate parentesi tonde per memorizzare in una variabile l’assegnazione delle coppie chiave/valore:
$variabile = (yield $chiave => $valore);
Per le assegnazioni non è invece considerato sintatticamente corretto il formato privo di parentesi.
Valori NULL e chiamate per riferimento
La parola chiave yield potrà essere introdotta anche senza che ad essa venga passato alcun argomento, in questo caso i valori gestiti saranno NULL e quindi, di fatto, dei non valori; a tal proposito è possibile presentare un semplice esempio come il seguente:
<?php
/*
generator senza passaggio di argomenti a yield
*/
function valori_null() {
foreach (range(5, 10) as $x) {
yield;
}
}
var_dump(iterator_to_array(valori_null()));
?>
Da notare che in questo caso yield raccoglierà l’informazione relativa al numero di iterazioni previste ma, contestualmente, non metterà a disposizione alcun valore, o meglio, fornirà unicamente valori NULL; la funzione iterator_to_array()
permetterà di copiare il contenuto dell’iterator all’interno di un array, per cui sarà possibile ottenere un output in cui gli elementi del primo verranno proposti come valori indicizzati di un vettore:
array(6) {
[0]=> NULL [1]=> NULL [2]=> NULL [3]=> NULL [4]=> NULL [5]=> NULL
}
Un altro caso particolare riguarda il passaggio di valori per riferimento, esso viene gestito ponendo il simbolo della “e commerciale” o ampersand (“&”) davanti al nome della funzione, lo stesso simbolo, detto anche reference operator, precederà poi il secondo argomento del ciclo foreach, cioè la variabile introdotta da “as” che sarà destinata all’assegnazione dei valori nel corso del ciclo; tale meccanica è praticamente la stessa adottata per la restituzione di reference da funzioni, essa potrà essere verificata attraverso un esempio sul modello del seguente:
<?php
/*
passaggio di valori
per riferimento nei generators
*/
function &countdown() {
$start = 5;
while ($start > 0) {
yield $start;
}
}
foreach (countdown() as &$esito) {
echo (--$esito). "<br/ >n";
}
?>
Nel caso specifico dello script proposto, la variabile $esito
verrà decrementata unitariamente ad ogni iterazione del ciclo, il numero di iterazioni del foreach sarà stabilito precedentemente tramite l’assegnazione alla variabile $start
di un valore passato come riferimento e affidato all’istruzione yield.
Gli oggetti della classe Generator
Nel momento in cui una funzione associata ad un generator viene richiamata per la prima volta si verifica la restituzione di un oggetto della classe Generator; il compito di tale oggetto è quello di implementare l’interfaccia Iterator che a livello semantico condivide alcuni metodi con Generator.
Nello specifico Generator mette a disposizione le seguenti funzionalità con livello di visibilità public:
- rewind(): “riavvolge” l’iterator, verrà lanciata un’eccezione in caso di iterazione già iniziata;
- valid(): restituisce “false” quando l’iterator è stato chiuso, diversamente restituisce “true”;
- current(): restituisce il valore fornito da yield al momento corrente;
- key(): restituisce la chiave associata al valore fornito da yield al momento corrente;
- next(): permette di riprendere l’esecuzione del generator;
- send(): fornisce valori al generator determinando la ripresa della sua esecuzione.
send()
non è un metodo comune con Iterator ed è stato introdotto specificatamente per l’invio di valori alla funzione del generator; si analizzi a questo proposito il seguente esempio:
<?php
/*
utilizzo del metodo send()
per l’invio di valori alla funzione del generator
*/
function invio() {
while (true) {
$testo = yield;
echo $testo;
}
}
$obj = invio();
$obj->send('HTML.it');
?>
Come è possibile notare, il valore inviato alla funzione del generator verrà fornito dall’istruzione yield e in questo modo potrà essere impiegato esattamente come qualsiasi altra variabile tramite la funzione stessa.
Da sottolineare anche il fatto che l’oggetto di classe Generator non potrà essere istanziato tramite la parola chiave “new”.
Conclusioni
I generators sono una delle novità di PHP 5.5, essi consentono di iterare set di dati senza che a questo scopo vengano allocati array in memoria, rappresentano inoltre un’alternativa valida all’implementazione di iterators; in questa breve trattazione è stata descritta la sintassi corretta per il loro utilizzo e sono stati mostrati alcuni esempi pratici riguardanti il loro impiego tramite le istruzioni basate sulla parola chiave yield.