| 1 |  |  | <?php | 
            
                                                                                                            
                            
            
                                    
            
            
                | 2 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 3 |  |  | declare(strict_types=1); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 4 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 5 |  |  | namespace Spiral\Exceptions; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 6 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 7 |  |  | use Closure; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 8 |  |  | use Spiral\Exceptions\Renderer\PlainRenderer; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 9 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 10 |  |  | /** | 
            
                                                                                                            
                            
            
                                    
            
            
                | 11 |  |  |  * The class is responsible for: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 12 |  |  |  *   - Global error handling (outside of dispatchers) using the {@see handleGlobalException()} method. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 13 |  |  |  *     Use the {@see register()} method to register the handler as a global exception/error catcher. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 14 |  |  |  *   - Runtime error handling (in a dispatcher after booting) using reporters and renderers. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 15 |  |  |  *     Use the {@see render()} method to prepare a formatted exception output. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 16 |  |  |  *     Use the {@see report()} method to send a debug information to configured channels. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 17 |  |  |  */ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 18 |  |  | class ExceptionHandler implements ExceptionHandlerInterface | 
            
                                                                                                            
                            
            
                                    
            
            
                | 19 |  |  | { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 20 |  |  |     public ?Verbosity $verbosity = Verbosity::BASIC; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 21 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 22 |  |  |     /** @var array<int, ExceptionRendererInterface> */ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 23 |  |  |     protected array $renderers = []; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 24 |  |  |     /** @var array<int, ExceptionReporterInterface|Closure> */ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 25 |  |  |     protected array $reporters = []; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 26 |  |  |     protected mixed $output = null; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 27 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 28 | 401 |  |     public function __construct() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 29 |  |  |     { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 30 | 401 |  |         $this->bootBasicHandlers(); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 31 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 32 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 33 | 38 |  |     public function register(): void | 
            
                                                                                                            
                            
            
                                    
            
            
                | 34 |  |  |     { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 35 | 38 |  |         \register_shutdown_function($this->handleShutdown(...)); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 36 | 38 |  |         \set_error_handler($this->handleError(...)); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 37 | 38 |  |         \set_exception_handler($this->handleGlobalException(...)); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 38 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 39 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 40 | 8 |  |     public function getRenderer(?string $format = null): ?ExceptionRendererInterface | 
            
                                                                                                            
                            
            
                                    
            
            
                | 41 |  |  |     { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 42 | 8 |  |         if ($format !== null) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 43 | 6 |  |             foreach ($this->renderers as $renderer) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 44 | 6 |  |                 if ($renderer->canRender($format)) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 45 | 6 |  |                     return $renderer; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 46 |  |  |                 } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 47 |  |  |             } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 48 |  |  |         } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 49 | 2 |  |         return \end($this->renderers) ?: null; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 50 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 51 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 52 | 5 |  |     public function render( | 
            
                                                                                                            
                            
            
                                    
            
            
                | 53 |  |  |         \Throwable $exception, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 54 |  |  |         ?Verbosity $verbosity = null, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 55 |  |  |         string $format = null, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 56 |  |  |     ): string { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 57 | 5 |  |         return (string)$this->getRenderer($format)?->render($exception, $verbosity ?? $this->verbosity, $format); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 58 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 59 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 60 |  |  |     public function canRender(string $format): bool | 
            
                                                                                                            
                            
            
                                    
            
            
                | 61 |  |  |     { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 62 |  |  |         return $this->getRenderer($format) !== null; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 63 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 64 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 65 | 12 |  |     public function report(\Throwable $exception): void | 
            
                                                                                                            
                            
            
                                    
            
            
                | 66 |  |  |     { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 67 | 12 |  |         foreach ($this->reporters as $reporter) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 68 |  |  |             try { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 69 | 3 |  |                 if ($reporter instanceof ExceptionReporterInterface) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 70 | 3 |  |                     $reporter->report($exception); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 71 |  |  |                 } else { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 72 | 3 |  |                     $reporter($exception); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 73 |  |  |                 } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 74 | 1 |  |             } catch (\Throwable) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 75 |  |  |                 // Do nothing | 
            
                                                                                                            
                            
            
                                    
            
            
                | 76 |  |  |             } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 77 |  |  |         } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 78 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 79 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 80 | 2 |  |     public function handleGlobalException(\Throwable $e): void | 
            
                                                                                                            
                            
            
                                    
            
            
                | 81 |  |  |     { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 82 | 2 |  |         if (\in_array(PHP_SAPI, ['cli', 'cli-server'], true)) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 83 | 2 |  |             $this->output ??= \defined('STDERR') ? \STDERR : \fopen('php://stderr', 'wb+'); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 84 | 2 |  |             $format = 'cli'; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 85 |  |  |         } else { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 86 |  |  |             $this->output ??= \defined('STDOUT') ? \STDOUT : \fopen('php://stdout', 'wb+'); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 87 |  |  |             $format = 'html'; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 88 |  |  |         } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 89 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 90 |  |  |         // we are safe to handle global exceptions (system level) with maximum verbosity | 
            
                                                                                                            
                            
            
                                    
            
            
                | 91 | 2 |  |         $this->report($e); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 92 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 93 |  |  |         // There is possible an exception on the application termination | 
            
                                                                                                            
                            
            
                                    
            
            
                | 94 |  |  |         try { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 95 | 2 |  |             \fwrite($this->output, $this->render($e, verbosity: $this->verbosity, format: $format)); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 96 |  |  |         } catch (\Throwable) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 97 |  |  |             $this->output = null; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 98 |  |  |         } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 99 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 100 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 101 |  |  |     /** | 
            
                                                                                                            
                            
            
                                    
            
            
                | 102 |  |  |      * Add renderer to the beginning of the renderers list | 
            
                                                                                                            
                            
            
                                    
            
            
                | 103 |  |  |      */ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 104 | 395 |  |     public function addRenderer(ExceptionRendererInterface $renderer): void | 
            
                                                                                                            
                            
            
                                    
            
            
                | 105 |  |  |     { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 106 | 395 |  |         \array_unshift($this->renderers, $renderer); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 107 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 108 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 109 |  |  |     /** | 
            
                                                                                                            
                            
            
                                    
            
            
                | 110 |  |  |      * @param ExceptionReporterInterface|Closure(\Throwable):void $reporter | 
            
                                                                                                            
                            
            
                                    
            
            
                | 111 |  |  |      */ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 112 | 3 |  |     public function addReporter(ExceptionReporterInterface|Closure $reporter): void | 
            
                                                                                                            
                            
            
                                    
            
            
                | 113 |  |  |     { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 114 | 3 |  |         $this->reporters[] = $reporter; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 115 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 116 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 117 |  |  |     /** | 
            
                                                                                                            
                            
            
                                    
            
            
                | 118 |  |  |      * @param resource $output | 
            
                                                                                                            
                            
            
                                    
            
            
                | 119 |  |  |      */ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 120 | 1 |  |     public function setOutput(mixed $output): void | 
            
                                                                                                            
                            
            
                                    
            
            
                | 121 |  |  |     { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 122 | 1 |  |         $this->output = $output; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 123 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 124 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 125 |  |  |     /** | 
            
                                                                                                            
                            
            
                                    
            
            
                | 126 |  |  |      * Handle php shutdown and search for fatal errors. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 127 |  |  |      */ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 128 |  |  |     protected function handleShutdown(): void | 
            
                                                                                                            
                            
            
                                    
            
            
                | 129 |  |  |     { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 130 |  |  |         if (empty($error = \error_get_last())) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 131 |  |  |             return; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 132 |  |  |         } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 133 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 134 |  |  |         try { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 135 |  |  |             $this->handleError($error['type'], $error['message'], $error['file'], $error['line']); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 136 |  |  |         } catch (\Throwable $e) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 137 |  |  |             $this->handleGlobalException($e); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 138 |  |  |         } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 139 |  |  |     } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 140 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 141 |  |  |     /** | 
            
                                                                                                            
                            
            
                                    
            
            
                | 142 |  |  |      * Convert application error into exception. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 143 |  |  |      * Handler for the {@see \set_error_handler()}. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 144 |  |  |      * @throws \ErrorException | 
            
                                                                                                            
                            
            
                                    
            
            
                | 145 |  |  |      */ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 146 | 12 |  |     protected function handleError( | 
            
                                                                                                            
                            
            
                                    
            
            
                | 147 |  |  |         int $errno, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 148 |  |  |         string $errstr, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 149 |  |  |         string $errfile = '', | 
            
                                                                                                            
                            
            
                                    
            
            
                | 150 |  |  |         int $errline = 0, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 151 |  |  |     ): bool { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 152 | 12 |  |         if (!(\error_reporting() & $errno)) { | 
            
                                                                                                            
                            
            
                                    
            
            
                | 153 | 2 |  |             return false; | 
            
                                                                                                            
                            
            
                                    
            
            
                | 154 |  |  |         } | 
            
                                                                                                            
                            
            
                                    
            
            
                | 155 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 156 | 12 |  |         throw new \ErrorException($errstr, $errno, 0, $errfile, $errline); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 157 |  |  |     } | 
            
                                                                                                            
                                                                
            
                                    
            
            
                | 158 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 159 | 393 |  |     protected function bootBasicHandlers(): void | 
            
                                                                        
                            
            
                                    
            
            
                | 160 |  |  |     { | 
            
                                                                        
                            
            
                                    
            
            
                | 161 | 393 |  |         $this->addRenderer(new PlainRenderer()); | 
            
                                                                                                            
                            
            
                                    
            
            
                | 162 |  |  |     } | 
            
                                                                                                            
                                                                
            
                                    
            
            
                | 163 |  |  | } | 
            
                                                        
            
                                    
            
            
                | 164 |  |  |  |