Lines
90.47%
19 / 21
Methods
80.00%
4 / 5
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| report | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| render | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| reportException | 100.00% | 3 / 3 | 100.00% | 1 / 1 | 3 | ||
| renderException | 100.00% | 6 / 6 | 100.00% | 1 / 1 | 4 | ||
| matches | 75.00% | 6 / 8 | 0.00% | 0 / 1 | 4.25 | ||
| 23 | final class Exceptions | |
| 24 | { | |
| 25 | /** @var array<callable(Throwable $e, ServerRequestInterface $request): void> */ | |
| 26 | private array $reportCallbacks = []; | |
| 27 | ||
| 28 | /** @var array<callable(Throwable $e, ServerRequestInterface $request): ?ResponseInterface> */ | |
| 29 | private array $renderCallbacks = []; | |
| 30 | ||
| 31 | /** | |
| 32 | * Register a report callback. | |
| 33 | * | |
| 34 | * The callback is invoked for every exception whose type matches the | |
| 35 | * type-hint of its first parameter. All matching report callbacks run. | |
| 36 | * | |
| 37 | * @param callable(Throwable $e, ServerRequestInterface $request): void $callback | |
| 38 | * @return $this | |
| 39 | */ | |
| 40 | public function report(callable $callback): self | |
| 41 | { | |
| 42 | $this->reportCallbacks[] = $callback; | |
| 43 | return $this; | |
| 44 | } | |
| 45 | ||
| 46 | /** | |
| 47 | * Register a render callback. | |
| 48 | * | |
| 49 | * The callback is invoked for every exception whose type matches the | |
| 50 | * type-hint of its first parameter. The first callback to return a | |
| 51 | * Response wins; a callback that returns null (or nothing) falls | |
| 52 | * through to the next callback, then to the framework default. | |
| 53 | * | |
| 54 | * @param callable(Throwable $e, ServerRequestInterface $request): ?ResponseInterface $callback | |
| 55 | * @return $this | |
| 56 | */ | |
| 57 | public function render(callable $callback): self | |
| 58 | { | |
| 59 | $this->renderCallbacks[] = $callback; | |
| 60 | return $this; | |
| 61 | } | |
| 62 | ||
| 63 | /** | |
| 64 | * Run every report callback whose first-param type matches $e. | |
| 65 | * | |
| 66 | * $request is passed by value. Callbacks that need to stash state on | |
| 67 | * the request should use the shared mutable RequestContext bag, e.g. | |
| 68 | * RequestContext::fromRequest($request)?->set('key', $value), rather | |
| 69 | * than reassigning the request. | |
| 70 | */ | |
| 71 | public function reportException(Throwable $e, ServerRequestInterface $request): void | |
| 72 | { | |
| 73 | foreach ($this->reportCallbacks as $callback) { | |
| 74 | if ($this->matches($callback, $e)) { | |
| 75 | $callback($e, $request); | |
| 76 | } | |
| 77 | } | |
| 78 | } | |
| 79 | ||
| 80 | /** | |
| 81 | * Return the first render callback's Response that matches $e, | |
| 82 | * or null to fall through to the framework default. | |
| 83 | */ | |
| 84 | public function renderException(Throwable $e, ServerRequestInterface $request): ?ResponseInterface | |
| 85 | { | |
| 86 | foreach ($this->renderCallbacks as $callback) { | |
| 87 | if ($this->matches($callback, $e)) { | |
| 88 | $response = $callback($e, $request); | |
| 89 | if ($response instanceof ResponseInterface) { | |
| 90 | return $response; | |
| 91 | } | |
| 92 | } | |
| 93 | } | |
| 94 | return null; | |
| 95 | } | |
| 96 | ||
| 97 | /** | |
| 98 | * Match a callback to an exception by the type-hint of its first | |
| 99 | * parameter. A callback typed to Throwable matches everything; one typed | |
| 100 | * to a specific class matches only that class (and subclasses). | |
| 101 | */ | |
| 102 | private function matches(callable $callback, Throwable $e): bool | |
| 103 | { | |
| 104 | $ref = new ReflectionFunction(\Closure::fromCallable($callback)); | |
| 105 | $params = $ref->getParameters(); | |
| 106 | if ($params === []) { | |
| 107 | return false; | |
| 108 | } | |
| 109 | $type = $params[0]->getType(); | |
| 110 | if ($type === null || !($type instanceof ReflectionNamedType)) { | |
| 111 | return false; | |
| 112 | } | |
| 113 | return is_a($e, $type->getName()); | |
| 114 | } | |
| 115 | } |