Search code examples
phpclosuresgeneratoryield

Is there a way to test if a closure is also a generator?


I am working with a PHP class which needs to accept multiple types of iterators and encompass them inside a unified wrapper. One of the types of iterators I need to support (and can!) is an anonymous function containing the yield keyword -- an anonymous generator, essentially.

Is there a way in PHP to test whether or not an anonymous function is a generator? The following is a list of methods I have tried (designed to show the outputs, not how I used them):

$anon = function() { yield 1; yield 2; };   // build anonymous generator
gettype($anon);                             // "object"
$anon instanceof \Traversable;              // 0
$anon instanceof \Iterable;                 // 0
$anon instanceof \IteratorAggregate;        // 0
$anon instanceof \Generator;                // 0
$anon instanceof \Closure;                  // 1 -- but doesn't tell me whether or not the closure is actually generator

$anon = $anon();                            // invoke, then run the same checks
gettype($anon);                             // "object"
$anon instanceof \Traversable;              // 1 (!!!)
$anon instanceof \Iterable;                 // 0
$anon instanceof \IteratorAggregate;        // 0
$anon instanceof \Generator;                // 1 (!!!)
$anon instanceof \Closure;                  // 0

As you can see above, I can invoke the anonymous function and then determine whether or not the function is a traversable type, but in order to implement this in a lazy-loading fashion (for example, an anonymous function wrapper around a SQL statement call followed by a yield of each record), I cannot invoke the anonymous function before the foreach iteration.

Are there any methods / types in PHP which I am missing that can be used to determine whether or not an anonymous method which has not yet been invoked is a generator?


Solution

  • $anon = function() { echo 'INVOKED', PHP_EOL; yield 1; yield 2; };
    $r = new ReflectionFunction($anon);
    var_dump($r->isGenerator());
    

    shows

    bool(true);
    

    INVOKED isn't displayed at all, proving that the closure isn't invoked at any time