Generators

A PHP function that contains a yield keyword is called a "generator function".

function foo() {
	echo "hi!\n";
	yield;
	echo "working hard.\n";
	yield;
	echo "bye!\n";
}

When you call this function, it does not do anything (it doesn't even echo "hi"). Instead, you get a Generator object, which lets you control the execution of the function.

Let's tell PHP to start running this function:

$generator = foo();
echo "Let's start foo\n";
$generator->rewind();
echo "foo stopped\n";

You will get this output:

Let's start foo
hi!
foo stopped

The function stops when there is a yield statement. We can tell the function to continue running using the Generator object:

$generator->send(null);

And this additional output:

working hard.

Now it stops again at the next yield.

Sending data into/out of the Generator

We can put a value behind the yield keyword to send data to the controller:

function bar() {
	yield 1;
}
$generator = bar();
$generator->rewind();
var_dump($generator->current());
int(1)

Similarly, we can send data back to the function. If you use yield [value] as an expression, it is resolved into the value passed in $generator->send().

function bar() {
	$receive = yield;
	var_dump($receive);
}
$generator = bar();
$generator->rewind();
$generator->send(2);
int(2)

Furthermore, the function can eventually "return" a value. This return value is not handled the same way as a yield; it is obtained using $generator->getReturn(). However, the return type hint must always be Generator no matter what you return, or if you don't return:

function qux(): Generator {
	yield 1;
	return 2;
}

Calling another generator

You can call another generator in a generator, which will pass through all the yielded values and send back all the sent values using the yield from syntax. The yield from expression resolves to the return value of the generator.

function test($value): Generator {
	$send = yield $value;
	return $send;
}

function main(): Generator {
	$a = yield from test(1);
	$b = yield from test(2);
	var_dump($a + $b);
}

$generator = main();
$generator->rewind();
var_dump($generator->current());
$generator->send(3);
var_dump($generator->current());
$generator->send(4);
int(1)
int(2)
int(7)

Hacking generators

Sometimes we want to make a generator function that does not yield at all. In that case, you can write 0 && yield; at the start of the function; this will make your function a generator function, but it will not yield anything. As of PHP 7.4.0, 0 && yield; is a no-op, which means it will not affect your program performance even if you run this line many times.

function emptyGenerator(): Generator {
	0 && yield;
	return 1;
}

$generator = emptyGenerator();
var_dump($generator->next());
var_dump($generator->getReturn());
NULL
int(1)