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;
}

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)