Exception Handling¶
Default behavior¶
One of the main benefits of using contexts is their “unroll” feature which
works even when an exception occurs in a user-provided callback. This means,
that exitContext()
is
invoked, even if the user’s code execution gets interrupted by an exception. To
illustrate this, we’ll slightly modify the example from the section named
Multiple context arguments. We’ll use same MyInt
objects as context
managers for all context arguments
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class MyInt implements ContextManagerInterface
{
public $value;
public function __construct(int $value)
{
$this->value = $value;
}
public function enterContext()
{
echo "enter: " . $this->value . "\n";
return $this->value;
}
public function exitContext(?\Throwable $exception = null) : bool
{
echo "exit: " . $this->value . "\n";
return false;
}
}
|
Instead of doing anything useful, we’ll just throw our custom exception
MyException
from the context (later):
1 2 3 | class MyException extends Exception
{
}
|
The exception handling and unroll process may be demonstrated with the
following snippet. We expect all the values 1, 2, and 3 to be printed at enter
and the same numbers in reversed order printed when context exits. Finally, we
should also receive MyException
.
1 2 3 4 5 6 7 8 | try {
with(new MyInt(1), new MyInt(2), new MyInt(3))(function (int ...$args) {
throw new MyException('my error message');
});
} catch (MyException $e) {
fprintf(STDERR, "%s\n", $e->getMessage());
exit(1);
}
|
The outputs from above snippet shall be
stdout:
1 2 3 4 5 6
enter: 1 enter: 2 enter: 3 exit: 3 exit: 2 exit: 1
stderr:
1
my error message
Handling exceptions in exitContext¶
If one of the context managers returns true
from its
exitContext()
,
all the remaining context managers will receive null
as $exception
argument and the exception will be treated as handled (it will not be
propagated to the context caller). To demonstrate this, let’s consider the
following modified MyInt
class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class MyInt implements ContextManagerInterface
{
public $value;
public $handle;
public function __construct(int $value, bool $handle = false)
{
$this->value = $value;
$this->handle = $handle;
}
public function enterContext()
{
echo "enter: " . $this->value . "\n";
return $this->value;
}
public function exitContext(?\Throwable $exception = null) : bool
{
echo "exit: " . $this->value . " (" . strtolower(gettype($exception)) . ")\n";
return $this->handle;
}
}
|
The object may be configured to return true
or false
. What happens when
one of the context managers returns true
, may be explained by the following
snippet
1 2 3 | with(new MyInt(1), new MyInt(2), new MyInt(3, true), new MyInt(4))(function (int ...$args) {
throw new MyException('my error message');
});
|
When unrolling, the objects MyInt(4)
and MyInt(3, true)
receive an
instance of MyException
as $exception
, then MyInt(3, true)
returns
true and the remaining objects MyInt(2)
and MyInt(1)
receive null
as $exception
. The exception thrown from user-provided callback is not
propagated to the outside. The code from the above snippet runs without an
exception and outputs the following text
1 2 3 4 5 6 7 8 | enter: 1
enter: 2
enter: 3
enter: 4
exit: 4 (object)
exit: 3 (object)
exit: 2 (null)
exit: 1 (null)
|