International PHP Conference 2015

Generatoren Übersicht

(PHP 5 >= 5.5.0)

Generatoren stellen einen einfachen Weg bereit einfache Iterationen zu erstellen, ohne den Overhead oder die Komplexität zur Erstellung einer Klasse zu haben, die das Iterator Interface implementieren.

Ein Generator ermöglicht es Code zu schreiben, der foreach nutzt, um über ein Set von Daten zu iterieren, ohne ein Array im Speicher zu bilden, was zur Überschreitung des Speicherlimits führen kann oder es eine beträchtliche Prozessor-Zeit zum Generieren benötigt. Alternativ, können Sie eine Generator Funktion schreiben, die die selbe wie die normale Funktion ist, ausser dass nicht einmal eine return Ausgabe erfolgt, sondern der Generator die Rückgabe (siehe: yield) selbst erhöht, und zwar so oft wie es nötig ist bis die Werte der Iteration vorbei sind.

Ein einfaches Beispiel dazu ist es die range() Funktion durch einen Generator neu zu re-implementieren. Die Standard range() Funktion wird ein Array generieren, welches jeden Wert enthält und diesen zurückliefert, die in einem großen Array resultiert. Beispiel: der Aufruf range(0, 1000000) wird in etwa über 100 MB an Speicher nutzen.

Als eine Alternative können wir einen xrange() Generator implementieren, welcher nur ein einziges mal genug Speicher benötigt, um das Iterator Objekt zu erstellen und den aktuellen Zustand des Generators intern nachzuverfolgen, was nur etwas weniger als 1 Kilobyte benötigt.

Beispiel #1 Implementierung einer range() als ein Generator

<?php
function xrange($start$limit$step 1) {
    if (
$start $limit) {
        if (
$step <= 0) {
            throw new 
LogicException('Schrittweite muss +ve sein');
        }

        for (
$i $start$i <= $limit$i += $step) {
            
yield $i;
        }
    } else {
        if (
$step >= 0) {
            throw new 
LogicException('Schrittweite muss -ve sein');
        }

        for (
$i $start$i >= $limit$i += $step) {
            
yield $i;
        }
    }
}

/*
 * Hinweis: beide unteren Funktionen range() und xrange() 
 * erzeugen die gleiche Ausgabe.
 */

echo 'Einstellige ungerade Zahl aus range():  ';
foreach (
range(192) as $zahl) {
    echo 
"$zahl ";
}
echo 
"\n";

echo 
'Einstellige ungerade Zahl aus xrange(): ';
foreach (
xrange(192) as $zahl) {
    echo 
"$zahl ";
}
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

Einstellige ungerade Zahl aus range():  1 3 5 7 9 
Einstellige ungerade Zahl aus xrange(): 1 3 5 7 9 
add a note add a note

User Contributed Notes 4 notes

up
41
bloodjazman at gmail dot com
2 years ago
for the protection from the leaking of resources
see RFC https://wiki.php.net/rfc/generators#closing_a_generator

and use finnaly

sample code

function getLines($file) {
    $f = fopen($file, 'r');
    try {
        while ($line = fgets($f)) {
            yield $line;
        }
    } finally {
        fclose($f);
    }
}

foreach (getLines("file.txt") as $n => $line) {
    if ($n > 5) break;
    echo $line;
}
up
21
dc at libertyskull dot com
1 year ago
Same example, different results:

----------------------------------
           |  time  | memory, mb |
----------------------------------
| not gen  | 0.7589 | 146.75     |
|---------------------------------
| with gen | 0.7469 | 8.75       |
|---------------------------------

Time in results varying from 6.5 to 7.8 on both examples.
So no real drawbacks concerning processing speed.
up
14
lubaev
1 year ago
Abstract test.
<?php

$start_time
=microtime(true);
$array = array();
$result = '';
for(
$count=1000000; $count--;)
{
 
$array[]=$count/2;
}
foreach(
$array as $val)
{
 
$val += 145.56;
 
$result .= $val;
}
$end_time=microtime(true);

echo
"time: ", bcsub($end_time, $start_time, 4), "\n";
echo
"memory (byte): ", memory_get_peak_usage(true), "\n";

?>

<?php

$start_time
=microtime(true);
$result = '';
function
it()
{
  for(
$count=1000000; $count--;)
  {
    yield
$count/2;
  }
}
foreach(
it() as $val)
{
 
$val += 145.56;
 
$result .= $val;
}
$end_time=microtime(true);

echo
"time: ", bcsub($end_time, $start_time, 4), "\n";
echo
"memory (byte): ", memory_get_peak_usage(true), "\n";

?>
Result:
----------------------------------
           |  time  | memory, mb |
----------------------------------
| not gen  | 2.1216 | 89.25      |
|---------------------------------
| with gen | 6.1963 | 8.75       |
|---------------------------------
| diff     | < 192% | > 90%      |
----------------------------------
up
-1
damirchilo at outlook dot com
30 days ago
You can also declare generator functions in a function scope.

<?php
function gen_n($n){
    function
gen_t($len){
        for(
$i = 1; $i < $len; $i++)
            yield
$i;
    }
   
    foreach(
gen_t($n) as $out)
       
printf("%d, ", $out);

   
printf("%d", ++$out);
}

gen_n(15);

?>
To Top