yield
La parola chiave yield in PHP crea una funzione generatore che produce valori uno alla volta, mettendo in pausa e riprendendo l'esecuzione.
Introduzione
La parola chiave yield trasforma una normale funzione PHP in un generatore — una funzione che produce una sequenza di valori uno alla volta, mettendo in pausa dopo ogni valore e riprendendo da dove si era fermata quando viene richiesto il valore successivo. Questa pagina spiega come funziona yield, come restituire chiavi, come inviare valori a un generatore, la sintassi di delega yield from, e quando i generatori consentono di risparmiare memoria rispetto alla costruzione di un array completo.
Una funzione diventa un generatore nel momento in cui contiene almeno un yield. Chiamarla non esegue il corpo — restituisce un oggetto Generator. Il corpo viene eseguito solo durante l'iterazione, avanzando al yield successivo ad ogni passo. Poiché un generatore implementa l'interfaccia Iterator, di solito lo si consuma con un ciclo foreach.
Un primo generatore
Ogni yield consegna un valore al chiamante e congela la funzione fino a quando non viene richiesto il valore successivo.
myGenerator() restituisce un oggetto generatore invece di eseguire immediatamente. Il ciclo foreach estrae i valori da esso: ad ogni passaggio la funzione viene eseguita fino al prossimo yield, emette quel valore e si mette in pausa. L'output è:
Hello World !A differenza di una funzione che costruisce e restituisce un array con return, le tre stringhe non vengono mai mantenute in memoria contemporaneamente — ognuna viene prodotta su richiesta. Confronta questo con il modo in cui una funzione normale usa return per restituire un singolo valore e terminare.
Restituire chiavi con yield
Un generatore può restituire coppie chiave/valore con la sintassi yield $key => $value, esattamente come un array associativo. Le chiavi sono disponibili all'interno del foreach:
<?php
function settings()
{
yield "host" => "localhost";
yield "port" => 5432;
yield "user" => "admin";
}
foreach (settings() as $key => $value) {
echo "$key = $value\n";
}Output:
host = localhost
port = 5432
user = adminSe non si forniscono chiavi, PHP numera automaticamente i valori restituiti a partire da 0, proprio come un array indicizzato.
I generatori sono lazy: un range efficiente in termini di memoria
Il vero vantaggio di yield è che i valori vengono calcolati solo quando necessario. Un generatore che produce un milione di numeri utilizza una quantità costante di memoria, mentre un array di un milione di numeri li alloca tutti in anticipo.
<?php
function gen_range($start, $end)
{
for ($i = $start; $i <= $end; $i++) {
yield $i;
}
}
$sum = 0;
foreach (gen_range(1, 1000000) as $n) {
$sum += $n;
}
echo $sum;Output:
500000500000Il ciclo non costruisce mai un array [1, 2, …, 1000000]; esiste un solo intero alla volta. Questo è il pattern da utilizzare quando si leggono file di grandi dimensioni riga per riga o si trasmettono righe da un database in streaming.
Restituire un valore da un generatore
Un generatore può anche usare return per esporre un valore finale (PHP 7+). Il valore di ritorno non fa parte dell'iterazione — lo si legge in seguito con getReturn():
<?php
function counter()
{
yield 1;
yield 2;
return "done";
}
$gen = counter();
foreach ($gen as $value) {
echo $value . " ";
}
echo $gen->getReturn();Output:
1 2 doneChiamare getReturn() prima che il generatore abbia terminato l'iterazione genera un'eccezione, quindi è necessario esaurirlo prima.
Inviare valori con send()
I generatori sono bidirezionali: un'espressione yield può anche ricevere un valore inviato dal chiamante tramite send(). Questa è la base delle coroutine.
<?php
function echoTimes()
{
while (true) {
$received = yield;
echo "Got: $received\n";
}
}
$gen = echoTimes();
$gen->current(); // prime the generator (run up to the first yield)
$gen->send("a");
$gen->send("b");Output:
Got: a
Got: bsend($value) riprende il generatore, facendo sì che l'espressione yield in pausa si valuti a $value, poi viene eseguita fino al prossimo yield.
Delega con yield from
yield from (PHP 7+) restituisce ogni valore da un altro generatore, array o Traversable, appiattendolo nel generatore corrente senza un ciclo manuale:
<?php
function inner()
{
yield 2;
yield 3;
}
function outer()
{
yield 1;
yield from inner();
yield from [4, 5];
}
foreach (outer() as $value) {
echo $value . " ";
}Output:
1 2 3 4 5 Quando usare un generatore
- Sequenze grandi o illimitate — lettura di un file multi-gigabyte o di uno stream infinito in cui è impossibile mantenere tutto in un array.
- Valori costosi che potrebbero non essere necessari — se il consumatore potrebbe fermarsi prima (
break), un generatore evita di calcolare il resto. - Codice di iterazione più pulito — sostituire una classe
Iteratorpersonalizzata con una singola funzione.
Evita i generatori quando hai bisogno di accesso casuale ($arr[42]), di contare i risultati in modo economico con count(), o di iterare gli stessi dati più volte — un generatore può essere attraversato solo una volta. In questi casi, costruisci un array normale, eventualmente con funzioni PHP come array_map.