Generators overview
(PHP 5 >= 5.5.0, PHP 7)
Generators provide an easy way to implement simple iterators without the overhead or complexity of implementing a class that implements the Iterator interface.
A generator allows you to write code that uses foreach to iterate over a set of data without needing to build an array in memory, which may cause you to exceed a memory limit, or require a considerable amount of processing time to generate. Instead, you can write a generator function, which is the same as a normal function, except that instead of returning once, a generator can yield as many times as it needs to in order to provide the values to be iterated over.
A simple example of this is to reimplement the range() function as a generator. The standard range() function has to generate an array with every value in it and return it, which can result in large arrays: for example, calling range(0, 1000000) will result in well over 100 MB of memory being used.
As an alternative, we can implement an xrange() generator, which will only ever need enough memory to create an Iterator object and track the current state of the generator internally, which turns out to be less than 1 kilobyte.
Пример #1 Implementing range() as a generator
<?php
function xrange($start, $limit, $step = 1) {
if ($start < $limit) {
if ($step <= 0) {
throw new LogicException('Step must be +ve');
}
for ($i = $start; $i <= $limit; $i += $step) {
yield $i;
}
} else {
if ($step >= 0) {
throw new LogicException('Step must be -ve');
}
for ($i = $start; $i >= $limit; $i += $step) {
yield $i;
}
}
}
/*
* Note that both range() and xrange() result in the same
* output below.
*/
echo 'Single digit odd numbers from range(): ';
foreach (range(1, 9, 2) as $number) {
echo "$number ";
}
echo "\n";
echo 'Single digit odd numbers from xrange(): ';
foreach (xrange(1, 9, 2) as $number) {
echo "$number ";
}
?>
Результат выполнения данного примера:
Single digit odd numbers from range(): 1 3 5 7 9 Single digit odd numbers from xrange(): 1 3 5 7 9
Generator objects
When a generator function is called for the first time, an object of the internal Generator class is returned. This object implements the Iterator interface in much the same way as a forward-only iterator object would, and provides methods that can be called to manipulate the state of the generator, including sending values to and returning values from it.
Коментарии
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;
}
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% |
----------------------------------
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.
Here's how to detect loop breaks, and how to handle or cleanup after an interruption.
<?php
function generator()
{
$complete = false;
try {
while (($result = some_function())) {
yield $result;
}
$complete = true;
} finally {
if (!$complete) {
// cleanup when loop breaks
} else {
// cleanup when loop completes
}
}
// Do something only after loop completes
}
?>
Bear in mind that execution of a generator function is postponed until iteration over its result (the Generator object) begins. This might confuse one if the result of a generator is assigned to a variable instead of immediate iteration.
<?php
$some_state = 'initial';
function gen() {
global $some_state;
echo "gen() execution start\n";
$some_state = "changed";
yield 1;
yield 2;
}
function peek_state() {
global $some_state;
echo "\$some_state = $some_state\n";
}
echo "calling gen()...\n";
$result = gen();
echo "gen() was called\n";
peek_state();
echo "iterating...\n";
foreach ($result as $val) {
echo "iteration: $val\n";
peek_state();
}
?>
If you need to perform some action when the function is called and before the result is used, you'll have to wrap your generator in another function.
<?php
/**
* @return Generator
*/
function some_generator() {
global $some_state;
$some_state = "changed";
return gen();
}
?>
In addition to the note of "montoriusz at gmail dot com": https://www.php.net/manual/en/language.generators.overview.php#119275
"If you need to perform some action when the function is called and before the result is used, you'll have to wrap your generator in another function."
You can use Generator::rewind instead (https://www.php.net/manual/en/generator.rewind.php)
Sample code:
<?php
/** function/generator definition **/
echo "calling gen()...\n";
$result = gen();
$result->rewind();
echo "gen() was called\n";
/** iteration **/
?>