|
1 | 1 | --- |
2 | 2 | title: Exception handling |
3 | | -description: "" |
4 | | -hidden: true |
| 3 | +description: "Learn how to gracefully handle exceptions in your application by writing exception processors." |
5 | 4 | --- |
| 5 | + |
| 6 | +## Overview |
| 7 | + |
| 8 | +Tempest comes with its own exception handler, which provides a simple way to catch and process exceptions. During local development, Tempest uses [Whoops](https://github.com/filp/whoops) to display detailed error pages. In production, it will show a generic error page. |
| 9 | + |
| 10 | +When an exception is thrown, it will be caught and piped through the registered exception processors. By default, the only registered exception processor, {b`Tempest\Core\LogExceptionProcessor`}, will simply log the exception. |
| 11 | + |
| 12 | +Of course, you may create your own exception processors. This is done by creating a class that implements the {`Tempest\Core\ExceptionProcessor`} interface. Classes implementing this interface are automatically [discovered](../4-internals/02-discovery.md), so you don't need to register them manually. |
| 13 | + |
| 14 | +## Reporting exceptions |
| 15 | + |
| 16 | +Sometimes, you may want to report an exception without necessarily throwing it. For example, you may want to log an exception, but not stop the execution of the application. To do this, you can use the `Tempest\report()` function. |
| 17 | + |
| 18 | +```php |
| 19 | +use function Tempest\report; |
| 20 | + |
| 21 | +try { |
| 22 | + // Some code that may throw an exception |
| 23 | +} catch (SomeException $e) { |
| 24 | + report($e); |
| 25 | +} |
| 26 | +``` |
| 27 | + |
| 28 | +## Disabling default logging |
| 29 | + |
| 30 | +Exception processors are discovered when Tempest boots, then stored in the `exceptionProcessors` property of {`Tempest\Core\AppConfig`}. The default logging processor, {b`Tempest\Core\LogExceptionProcessor`}, is automatically added to the list of processors. |
| 31 | + |
| 32 | +To disable exception logging, you may remove it in a `KernelEvent::BOOTED` event handler: |
| 33 | + |
| 34 | +```php |
| 35 | +use Tempest\Core\AppConfig; |
| 36 | +use Tempest\Core\KernelEvent; |
| 37 | +use Tempest\Core\LogExceptionProcessor; |
| 38 | +use Tempest\EventBus\EventHandler; |
| 39 | +use Tempest\Support\Arr; |
| 40 | + |
| 41 | +final readonly class DisableExceptionLogging |
| 42 | +{ |
| 43 | + public function __construct( |
| 44 | + private AppConfig $appConfig, |
| 45 | + ) { |
| 46 | + } |
| 47 | + |
| 48 | + #[EventHandler(KernelEvent::BOOTED)] |
| 49 | + public function __invoke(): void |
| 50 | + { |
| 51 | + Arr\forget_values($this->appConfig->exceptionProcessors, LogExceptionProcessor::class); |
| 52 | + } |
| 53 | +} |
| 54 | +``` |
| 55 | + |
| 56 | +## Adding context to exceptions |
| 57 | + |
| 58 | +Sometimes, an exception may have information that you would like to be logged. By implementing the {`Tempest\Core\HasContext`} interface on an exception class, you can provide additional context that will be logged—and available to other processors. |
| 59 | + |
| 60 | +```php |
| 61 | +use Tempest\Core\HasContext; |
| 62 | + |
| 63 | +final readonly class UserNotFound extends Exception implements HasContext |
| 64 | +{ |
| 65 | + public function __construct(private string $userId) |
| 66 | + { |
| 67 | + parent::__construct("User {$userId} not found."); |
| 68 | + } |
| 69 | + |
| 70 | + public function context(): array |
| 71 | + { |
| 72 | + return [ |
| 73 | + 'user_id' => $this->userId, |
| 74 | + ]; |
| 75 | + } |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +## Customizing the error page |
| 80 | + |
| 81 | +In production, when an uncaught exception occurs, Tempest displays a minimalistic, generic error page. You may customize this behavior by providing your own response to the {b`Tempest\Http\HttpException`}. |
| 82 | + |
| 83 | +For instance, you may display a branded error page by providing a view: |
| 84 | + |
| 85 | +```php |
| 86 | +use Tempest\Http\GenericResponse; |
| 87 | +use Tempest\Http\HttpException; |
| 88 | +use Throwable; |
| 89 | + |
| 90 | +final class UncaughtExceptionProcessor implements ExceptionProcessor |
| 91 | +{ |
| 92 | + public function process(Throwable $throwable): Throwable |
| 93 | + { |
| 94 | + if ($throwable instanceof HttpException) { |
| 95 | + $throwable->response = new GenericResponse( |
| 96 | + status: $throwable->status, |
| 97 | + body: view('./error.view.php', exception: $throwable), |
| 98 | + ); |
| 99 | + } |
| 100 | + |
| 101 | + return $throwable; |
| 102 | + } |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +## Testing |
| 107 | + |
| 108 | +By extending {`Tempest\Framework\Testing\IntegrationTest`} from your test case, you gain access to the exception testing utilities, which allow you to make assertions about reported exceptions. |
| 109 | + |
| 110 | +```php |
| 111 | +// Prevents exceptions from being actually processed |
| 112 | +$this->exceptions->preventReporting(); |
| 113 | + |
| 114 | +// Asserts that the exception was reported |
| 115 | +$this->exceptions->assertReported(UserNotFound::class); |
| 116 | + |
| 117 | +// Asserts that the exception was not reported |
| 118 | +$this->exceptions->assertNotReported(UserNotFound::class); |
| 119 | + |
| 120 | +// Asserts that no exceptions were reported |
| 121 | +$this->exceptions->assertNothingReported(); |
| 122 | +``` |
0 commit comments