DEV Community

Andrej Rypo
Andrej Rypo

Posted on • Edited on

Anonymous Generator in PHP

Recently, I wanted to create an anonymous generator. The good news is that it is possible, the syntax is not the most obvious one, though.
You need to use a technique called "immediately invoked function expression" mostly known from JavaScript of the jQuery era.

Here it is:

$iterable = (function (): Generator {
  yield 42; // yield any number of values
})();
Enter fullscreen mode Exit fullscreen mode

Explanation:

  1. the anonymous function itself is not a generator, it's just a callable and can not be iterated over
  2. the function is invoked immediately after it is created and must be enclosed in parentheses (function(){ ... })() if we want to invoke it right away
  3. the invocation results in a Generator, which can be iterated over
$func = function(): Generator { yield 42; };
$iterable = $func(); // this is when the generator is created

foreach($func as $item){ }     // error, $foo is not iterable
foreach($iterable as $item){ } // iterates over the generator
Enter fullscreen mode Exit fullscreen mode

Generators have been in PHP since version 5.5. I have since used them rarely, but they are good for solving some edge cases, like generating a sequence of unknown length.

A generator can be used to replace code like the one below. Yeah, for two items it might be useless, but for multiple such extractors it does make sense. Especially when you know only the first extractor will be used in 99% of cases.

function getDefaultExtractors(
  string $headerName, 
  string $cookieName
): iterable {
  #<  code here  >#
  return $extractors;
}
Enter fullscreen mode Exit fullscreen mode

Code with ifs and array push:

$extractors = [];
if ($headerName !== null) {
  $extractors[] = Make::headerExtractor($headerName);
}
if ($cookieName !== null) {
  $extractors[] = Make::cookieExtractor($cookieName);
}
Enter fullscreen mode Exit fullscreen mode

Code with array_filter and ternaries:

$extractors = array_filter([
  $headerName !== null ? Make::headerExtractor($headerName) : null,
  $cookieName !== null ? Make::cookieExtractor($cookieName) : null,
]);
Enter fullscreen mode Exit fullscreen mode

Code using a generator:

$extractors = (function () use ($headerName, $cookieName): Generator {
  $headerName !== null && yield Make::headerExtractor($headerName);
  $cookieName !== null && yield Make::cookieExtractor($cookieName);
})(),
Enter fullscreen mode Exit fullscreen mode

The key idea behind the generators is that the execution is actually paused on each yield statement, until the next iteration is initiated. For cases where the iteration is interrupted before all of the generated items are consumed, this saves some computation resources.

Top comments (0)