Lines
95.00%
19 / 20
Methods
50.00%
1 / 2
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| messages | 88.88% | 8 / 9 | 0.00% | 0 / 1 | 3.01 | ||
| render | 100.00% | 11 / 11 | 100.00% | 1 / 1 | 4 | ||
| 17 | final class ExceptionChain | |
| 18 | { | |
| 19 | /** | |
| 20 | * The chain as "Class: message" strings, outermost first. | |
| 21 | * | |
| 22 | * Exception::getPrevious() is final, so userland cannot build a cycle; | |
| 23 | * the visited-set is a defensive guard anyway (a native or exotic | |
| 24 | * Throwable implementation could) and costs nothing per link. | |
| 25 | * | |
| 26 | * @return list<string> | |
| 27 | */ | |
| 28 | public static function messages(\Throwable $e): array | |
| 29 | { | |
| 30 | $messages = []; | |
| 31 | $seen = []; | |
| 32 | ||
| 33 | for ($current = $e; $current !== null; $current = $current->getPrevious()) { | |
| 34 | $hash = spl_object_id($current); | |
| 35 | ||
| 36 | if (isset($seen[$hash])) { | |
| 37 | break; | |
| 38 | } | |
| 39 | ||
| 40 | $seen[$hash] = true; | |
| 41 | $messages[] = $current::class . ': ' . $current->getMessage(); | |
| 42 | } | |
| 43 | ||
| 44 | return $messages; | |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Render the chain as console lines: the first line carries the caller's | |
| 49 | * prefix (already styled by the caller), each cause follows as an | |
| 50 | * indented "Caused by" line, in red when styling is on. | |
| 51 | * | |
| 52 | * @param string $firstLine e.g. "Sync failed: " — the outermost message is appended | |
| 53 | * @param bool $colors Emit ANSI codes around the "Caused by" lines. Off | |
| 54 | * when the destination is not a TTY (pipes, CI logs, captured | |
| 55 | * output) — the styling decision belongs to the caller, which | |
| 56 | * knows where the string is headed; this method never inspects | |
| 57 | * the streams itself. | |
| 58 | * @return string Multi-line string without a trailing newline | |
| 59 | */ | |
| 60 | public static function render(\Throwable $e, string $firstLine, bool $colors = true): string | |
| 61 | { | |
| 62 | $messages = self::messages($e); | |
| 63 | ||
| 64 | $output = $firstLine . array_shift($messages); | |
| 65 | ||
| 66 | if (!$colors) { | |
| 67 | foreach ($messages as $message) { | |
| 68 | $output .= "\n ↳ Caused by " . $message; | |
| 69 | } | |
| 70 | ||
| 71 | return $output; | |
| 72 | } | |
| 73 | ||
| 74 | foreach ($messages as $message) { | |
| 75 | $output .= "\n" . ConsoleColors::FG_RED | |
| 76 | . ' ↳ Caused by ' . $message | |
| 77 | . ConsoleColors::RESET; | |
| 78 | } | |
| 79 | ||
| 80 | return $output; | |
| 81 | } | |
| 82 | } |