12th September 2014

PHP 5.5 Generators and Drupal

Chris Hilditch
Developer

PHP 5.5 introduces generator functions.  Generator functions return an object that can be iterated over - often to be used with a foreach loop, for example:

function gen_one_to_three() {
  for ($i = 1; $i <= 3; $i++) {
    // Note that $i is preserved between yields.
    yield $i;
  }
}
$generator = gen_one_to_three();

foreach ($generator as $value) {
  echo "$value\n";
}

Which will output:

1
2
3

(Example taken from php.net).

PHP calls the generator function when it needs values - when the generator yields a value the state of the generator is saved, so it can then be resumed when the next value is required. This can lead to significant performance boosts over creating an array which only exists to be looped over as less memory is needed.

There is more detail on http://codingexplained.com/coding/php/introduction-to-php-generators.

So how can this apply to Drupal …

A render array might look like …

$wrapper = array(
  '#type'; => 'container',
  '#attributes' => array(
    'class' => array('class'),
  ),
  'item-one' => array ( ... ),
  'item-two' => array ( ... ),
  'item-three' => array ( ... ),
);

Any array element where it’s key starts with a hash is a property of that item, anything else is a child.  At times you need to loop over the the children of any element to perform an operation.

A common Drupal pattern for doing this is -  

foreach (element_children($variables) as $key) {
  ...
  $variables[$key]['#example'] = 'example';
  dpm($variables[$key]);
  ...
}

The element_children function returns an array which contains the array keys of the children array elements. This breaks from the standard PHP foreach pattern where you perform operations directly on the value created by the foreach loops - I don’t think this is ideal - I had to look twice to see what was happening the first time I saw it.

Using generators, you can use a more typical php pattern - the following is equivalent to the above.

foreach(element_children_generator($variables) as $key => &$element) {
  ...
  $element['#example'] = 'example';
  dpm($element);
  ...
}

function &element_children_generator(&$vars) {
  foreach ($vars as $key => &$element) {
    if ($key === '' || $key[0] !== '#') {
      yield $key => $element;
    }
  }
}

As well as being a more typical PHP pattern, referencing the element within the loop is cleaner.

There are downsides to this approach too. A developer familiar with Drupal may have to look twice to see what is going on with the yield keyword. Obviously this can’t go into Drupal 7 Core (which supports php 5.2.5+), and I wouldn’t recommend it for Contrib either for the same reason. 

However since PHP 5.3 and below is EOL I think this pattern is well worth adopting in your own projects with low risk.