I have a generator that passes a collection of values into a method and yields the result. The method that is called may return an exception. When this happens, I would like the exception to fall through to the code that calls the generator to handle the exception, and then continue looping the generator.
To illustrate this, the following is an example of generator that will yield 1
, throw an \Exception
, then yield 3
.
/** @var \Generator $gen */
$gen = function () {
for ($i = 1; $i <= 3; $i++) {
if ($i == 2) {
throw new \Exception('Exception thrown for 2');
}
yield $i;
}
};
This is an example of my attempt to run this code such that I can get it to yield 3
$g = $gen();
var_export($g->current());
echo "\n";
try {
$g->next();
var_export($g->current());
echo "\n";
} catch (\Exception $e) {
echo $e->getMessage() . "\n";
}
try {
$g->next();
var_export($g->current());
echo "\n";
} catch (\Exception $e) {
echo $e->getMessage() . "\n";
}
The following is the output of the above code.
1
Exception thrown for 2.
NULL
So a repeated calls to next()
does nothing and current()
will return NULL
, where I would like the generator to continue past the Exception so I can get 3
.
Throwing the exception inside generator is closing it totally, that is why it returns "NULL" on third iteration. If you try $g->valid()
after throwing the exception you will get false
as the result.
All exceptions thrown inside generator should also be catched and handled inside it, you can even throw them from outside into the generator for handling using $g->throw()
method. For more info check the documentation
However, what you are trying to achieve is possible. You can yield
the exception, instead of throwing. This way you will not close the generator, and can handle the exception outside.
Try this code:
$gen = function () {
for ($i = 1; $i <= 3; $i++) {
// If something wrong happens
if ($i == 2) {
// Instead throwing the exception yield it
// that way we don't close the generator
yield new \Exception('Exception thrown for 2');
} else {
yield $i;
}
}
};
And testing it with:
$g = $gen();
for ($i = 0; $i < 3; $i++) {
$current = $g->current();
// Instead of catching, check if the yielded value is Exception
if ($current instanceof \Exception) {
// Handle the exception directly
// or throw it with throw $current to handle it in normal try-catch block
echo $current->getMessage() . "\n";
} else {
echo $current . "\n";
}
$g->next();
}
Gives you result:
1
Exception thrown for 2
3