<< previous article: Trust Me: Our Adservers Are Secure next article: Simple Solutions for Powerful Software >>

PHP 5.5: Generators

Sebastian Bergmann | 07/10/2013 | Blog

Last month, the first stable version of PHP 5.5 was released. It introduced the yield keyword that allows the implementation of generators and coroutines.

What is a generator?

A generator is an interruptible function that returns a sequence of values (using the yield keyword) instead of a single value (using the return keyword). Two things happen when the yield statement of a generator function is executed: the argument of the yield statement is yielded and the execution of the generator function is suspended. The execution of the generator function is resumed when the next value is requested.

How do you implement a generator in PHP?

The xrange() function in the example below is the "Hello world" example for generators:

              <?php
function xrange($start, $end, $step = 1) {
    for ($i = $start; $i <= $end; $i += $step) {
        yield $i;
    }
}

foreach (xrange(1, 1000000) as $num) {
    print $num, "\n";
}
            

Unlike the range() function that is built into PHP, the xrange() generator function shown above does not compute an array with all numbers. Instead, it returns an iterator that will emit one number per iteration step.

How do generators work in PHP?

In PHP, invoking a generator function creates an object (of the internal Generator class) that provides the Iterator interface and can thusly be used with foreach.

Generators can be thought of as a convenient way to implement iterators that does not require the manual implementation of the five methods of the built-in Iterator interface.

Use Case: Using Generators as Data Providers with PHPUnit

A PHPUnit test method can accept arbitrary arguments to decouple test code from test data. These arguments come from so-called data providers.

Since PHPUnit 3.7.22 it is possible to use a generator method that yields an array for each iteration step as a data provider. For each array that is part of this sequence the test method will be called with the contents of the array as its arguments.

              <?php
class SomethingTest extends PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider addressesProvider
     */
    public function testSomethingThatRequiresAnAddress(Address $address)
    {
        // ...
    }

    public function addressesProvider()
    {
        for ($i = 0; $i < 10; $i++) {
            yield array(
              new Address(
                // Random string with length between 8 and 16
                substr(
                  str_shuffle('abcdefghijklmnopqrstuvwxyz'),
                  0,
                  rand(8,16)
                ),

                // Random five digit number
                sprintf('%05d', rand(1, 99999)),

                // Random string with length between 8 and 16
                substr(
                  str_shuffle('abcdefghijklmnopqrstuvwxyz'),
                  0,
                  rand(8,16)
                ),

                // Random string with length 2
                substr(
                  str_shuffle('abcdefghijklmnopqrstuvwxyz'),
                  0,
                  2
                )
              )
            );
        }
    }
}
            

In the example above we want to test a unit of code that requires an Address value object. We want to test this unit of code with a sequence of random addresses. The addressesProvider() method yields such an object in each iteration step.

Current versions of PHPUnit unfortunately do not process the sequence of data provided by a data provider one by one. Due to the current design of PHPUnit's test runner, all elements of the provided sequence are processed before the tests are run. The promise of reduced memory usage and improved performance associated with iterators in general and generators in particular cannot be fulfilled because of this.

The case can be made, however, that there is a benefit to using an generator or iterator as a data provider due to improved code legibility and separation of concerns. And future versions of PHPUnit should be able to deal more elegantly and efficiently with the sequence of data provided by generators and iterators.

<< previous article: Trust Me: Our Adservers Are Secure next article: Simple Solutions for Powerful Software >>