If you’re a nerd and would love a very detailed post on Coroutines, here’s one for you – http://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html. If you decide to read the above mentioned article, I believe you can safely skip my article below, it doesn’t have anything extra, rather it tries to explain co-routines on a rush.
NOTE: The features of PHP described in this post were introduced on PHP 5.5.0. If you are using an older version, this is not going to work. Please upgrade your PHP version and enjoy the performance bonus with these awesome new additions! 🙂
Before we can start on Coroutines, let’s review our knowledge on Generators. Generators allow us to quickly create iterators which instead of populating a large dataset once, populate each item in turn, one after another. Since one item is produced/returned every turn, it is very memory efficient. In fact you can handle an unlimited stream of data with minimal memory usages using iterators. Let’s see a quick example:
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php function gen() { for($i=0; $i < 100; $i++) { yield $i; } } $x = gen(); foreach ($x as $value) { echo $value.PHP_EOL; } |
So what does it do? The gen() function returns an iterator of the “Generator” class. (If you look at the manual, Generator class implements the “Iterator” interface and thus we can use foreach on the class).
Internally, when we iterate over the generator object, it starts executing the gen() function. When it hits the yield statement, it pauses execution of the function and returns the value to the generator object (and thus back to our main loop). We then print the value. In the next iteration, the gen() function is resumed from where it was paused and executed until it hits the yield statement again.
Let’s see an example of generator which doesn’t use any loops inside and rather has multiple yield statements. This should help us in better understanding the use of yield in generators.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php function gen() { yield 1; yield 2; yield 3; yield 4; yield 5; } $x = gen(); foreach($x as $val) { echo $val.PHP_EOL; } |
The above example should explain better how Generators are actually interruptible functions which can be paused and resumed with the yield statement to make iteration possible on the returned values. Now, as long as Generators are concerned, the function returns value. This is a one way communication. But what if we want to send back a value to the function? Coroutines come into play. Coroutines allow two way communication through the “send()” method on a “Generator” class. When we send a value back to the function, yield switches it’s role. Instead of returning a value back to the iteration process, it then receives the value we send into the function. A very plain example to describe this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php function gen() { echo "[1] Incoming value: ".yield.PHP_EOL; echo "[2] Incoming value: ".yield.PHP_EOL; echo "[3] Incoming value: ".yield.PHP_EOL; echo "[4] Incoming value: ".yield.PHP_EOL; echo "[5] Incoming value: ".yield.PHP_EOL; } $x = gen(); $x->send(5); $x->send(4); $x->send(3); $x->send(2); $x->send(1); |
What’s happening here? We’re sending a value back and the value is available to the “yield” statement inside the function.
We can return and receive values with the same yield statement. Let’s see how:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php function gen() { echo "[1] Incoming value: ". (yield 1) . PHP_EOL; echo "[2] Incoming value: ". (yield 2) . PHP_EOL; echo "[3] Incoming value: ". (yield 3) . PHP_EOL; echo "[4] Incoming value: ". (yield 4) . PHP_EOL; echo "[5] Incoming value: ". (yield 5) . PHP_EOL; } $x = gen(); echo "Iterator value: ". $x->current(). PHP_EOL; echo "Iterator value: ". $x->send(5). PHP_EOL; echo "Iterator value: ". $x->send(4). PHP_EOL; echo "Iterator value: ". $x->send(3). PHP_EOL; echo "Iterator value: ". $x->send(2). PHP_EOL; echo "Iterator value: ". $x->send(1). PHP_EOL; |
Awesome no? Well, Coroutines are definitely complex and sometimes very hard to handle. If you don’t get the concepts at the first look or would love to understand more of the internal implementation, I recommend you read and understand the article I mentioned at the beginning. And do play with it. Have fun!