├── ExceptionDisplayerInterface.php ├── ExceptionServiceProvider.php ├── Handler.php ├── PlainDisplayer.php ├── SymfonyDisplayer.php ├── WhoopsDisplayer.php ├── composer.json └── resources └── plain.html /ExceptionDisplayerInterface.php: -------------------------------------------------------------------------------- 1 | registerDisplayers(); 18 | 19 | $this->registerHandler(); 20 | } 21 | 22 | /** 23 | * Register the exception displayers. 24 | * 25 | * @return void 26 | */ 27 | protected function registerDisplayers() 28 | { 29 | $this->registerPlainDisplayer(); 30 | 31 | $this->registerDebugDisplayer(); 32 | } 33 | 34 | /** 35 | * Register the exception handler instance. 36 | * 37 | * @return void 38 | */ 39 | protected function registerHandler() 40 | { 41 | $this->app['exception'] = $this->app->share(function($app) 42 | { 43 | return new Handler($app, $app['exception.plain'], $app['exception.debug']); 44 | }); 45 | } 46 | 47 | /** 48 | * Register the plain exception displayer. 49 | * 50 | * @return void 51 | */ 52 | protected function registerPlainDisplayer() 53 | { 54 | $this->app['exception.plain'] = $this->app->share(function($app) 55 | { 56 | // If the application is running in a console environment, we will just always 57 | // use the debug handler as there is no point in the console ever returning 58 | // out HTML. This debug handler always returns JSON from the console env. 59 | if ($app->runningInConsole()) 60 | { 61 | return $app['exception.debug']; 62 | } 63 | else 64 | { 65 | return new PlainDisplayer; 66 | } 67 | }); 68 | } 69 | 70 | /** 71 | * Register the Whoops exception displayer. 72 | * 73 | * @return void 74 | */ 75 | protected function registerDebugDisplayer() 76 | { 77 | $this->registerWhoops(); 78 | 79 | $this->app['exception.debug'] = $this->app->share(function($app) 80 | { 81 | return new WhoopsDisplayer($app['whoops'], $app->runningInConsole()); 82 | }); 83 | } 84 | 85 | /** 86 | * Register the Whoops error display service. 87 | * 88 | * @return void 89 | */ 90 | protected function registerWhoops() 91 | { 92 | $this->registerWhoopsHandler(); 93 | 94 | $this->app['whoops'] = $this->app->share(function($app) 95 | { 96 | // We will instruct Whoops to not exit after it displays the exception as it 97 | // will otherwise run out before we can do anything else. We just want to 98 | // let the framework go ahead and finish a request on this end instead. 99 | with($whoops = new Run)->allowQuit(false); 100 | 101 | $whoops->writeToOutput(false); 102 | 103 | return $whoops->pushHandler($app['whoops.handler']); 104 | }); 105 | } 106 | 107 | /** 108 | * Register the Whoops handler for the request. 109 | * 110 | * @return void 111 | */ 112 | protected function registerWhoopsHandler() 113 | { 114 | if ($this->shouldReturnJson()) 115 | { 116 | $this->app['whoops.handler'] = $this->app->share(function() 117 | { 118 | return new JsonResponseHandler; 119 | }); 120 | } 121 | else 122 | { 123 | $this->registerPrettyWhoopsHandler(); 124 | } 125 | } 126 | 127 | /** 128 | * Determine if the error provider should return JSON. 129 | * 130 | * @return bool 131 | */ 132 | protected function shouldReturnJson() 133 | { 134 | return $this->app->runningInConsole() || $this->requestWantsJson(); 135 | } 136 | 137 | /** 138 | * Determine if the request warrants a JSON response. 139 | * 140 | * @return bool 141 | */ 142 | protected function requestWantsJson() 143 | { 144 | return $this->app['request']->ajax() || $this->app['request']->wantsJson(); 145 | } 146 | 147 | /** 148 | * Register the "pretty" Whoops handler. 149 | * 150 | * @return void 151 | */ 152 | protected function registerPrettyWhoopsHandler() 153 | { 154 | $this->app['whoops.handler'] = $this->app->share(function() 155 | { 156 | with($handler = new PrettyPageHandler)->setEditor('sublime'); 157 | 158 | return $handler; 159 | }); 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /Handler.php: -------------------------------------------------------------------------------- 1 | debug = $debug; 72 | $this->plainDisplayer = $plainDisplayer; 73 | $this->debugDisplayer = $debugDisplayer; 74 | $this->responsePreparer = $responsePreparer; 75 | } 76 | 77 | /** 78 | * Register the exception / error handlers for the application. 79 | * 80 | * @param string $environment 81 | * @return void 82 | */ 83 | public function register($environment) 84 | { 85 | $this->registerErrorHandler(); 86 | 87 | $this->registerExceptionHandler(); 88 | 89 | if ($environment != 'testing') $this->registerShutdownHandler(); 90 | } 91 | 92 | /** 93 | * Register the PHP error handler. 94 | * 95 | * @return void 96 | */ 97 | protected function registerErrorHandler() 98 | { 99 | set_error_handler(array($this, 'handleError')); 100 | } 101 | 102 | /** 103 | * Register the PHP exception handler. 104 | * 105 | * @return void 106 | */ 107 | protected function registerExceptionHandler() 108 | { 109 | set_exception_handler(array($this, 'handleUncaughtException')); 110 | } 111 | 112 | /** 113 | * Register the PHP shutdown handler. 114 | * 115 | * @return void 116 | */ 117 | protected function registerShutdownHandler() 118 | { 119 | register_shutdown_function(array($this, 'handleShutdown')); 120 | } 121 | 122 | /** 123 | * Handle a PHP error for the application. 124 | * 125 | * @param int $level 126 | * @param string $message 127 | * @param string $file 128 | * @param int $line 129 | * @param array $context 130 | * 131 | * @throws \ErrorException 132 | */ 133 | public function handleError($level, $message, $file = '', $line = 0, $context = array()) 134 | { 135 | if (error_reporting() & $level) 136 | { 137 | throw new ErrorException($message, 0, $level, $file, $line); 138 | } 139 | } 140 | 141 | /** 142 | * Handle an exception for the application. 143 | * 144 | * @param \Exception $exception 145 | * @return \Symfony\Component\HttpFoundation\Response 146 | */ 147 | public function handleException(Exception $exception) 148 | { 149 | $response = $this->callCustomHandlers($exception); 150 | 151 | // If one of the custom error handlers returned a response, we will send that 152 | // response back to the client after preparing it. This allows a specific 153 | // type of exceptions to handled by a Closure giving great flexibility. 154 | if ( ! is_null($response)) 155 | { 156 | return $this->prepareResponse($response); 157 | } 158 | 159 | // If no response was sent by this custom exception handler, we will call the 160 | // default exception displayer for the current application context and let 161 | // it show the exception to the user / developer based on the situation. 162 | return $this->displayException($exception); 163 | } 164 | 165 | /** 166 | * Handle an uncaught exception. 167 | * 168 | * @param \Exception $exception 169 | * @return void 170 | */ 171 | public function handleUncaughtException($exception) 172 | { 173 | $this->handleException($exception)->send(); 174 | } 175 | 176 | /** 177 | * Handle the PHP shutdown event. 178 | * 179 | * @return void 180 | */ 181 | public function handleShutdown() 182 | { 183 | $error = error_get_last(); 184 | 185 | // If an error has occurred that has not been displayed, we will create a fatal 186 | // error exception instance and pass it into the regular exception handling 187 | // code so it can be displayed back out to the developer for information. 188 | if ( ! is_null($error)) 189 | { 190 | extract($error); 191 | 192 | if ( ! $this->isFatal($type)) return; 193 | 194 | $this->handleException(new FatalError($message, $type, 0, $file, $line))->send(); 195 | } 196 | } 197 | 198 | /** 199 | * Determine if the error type is fatal. 200 | * 201 | * @param int $type 202 | * @return bool 203 | */ 204 | protected function isFatal($type) 205 | { 206 | return in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE)); 207 | } 208 | 209 | /** 210 | * Handle a console exception. 211 | * 212 | * @param \Exception $exception 213 | * @return void 214 | */ 215 | public function handleConsole($exception) 216 | { 217 | return $this->callCustomHandlers($exception, true); 218 | } 219 | 220 | /** 221 | * Handle the given exception. 222 | * 223 | * @param \Exception $exception 224 | * @param bool $fromConsole 225 | * @return void 226 | */ 227 | protected function callCustomHandlers($exception, $fromConsole = false) 228 | { 229 | foreach ($this->handlers as $handler) 230 | { 231 | // If this exception handler does not handle the given exception, we will just 232 | // go the next one. A handler may type-hint an exception that it handles so 233 | // we can have more granularity on the error handling for the developer. 234 | if ( ! $this->handlesException($handler, $exception)) 235 | { 236 | continue; 237 | } 238 | elseif ($exception instanceof HttpExceptionInterface) 239 | { 240 | $code = $exception->getStatusCode(); 241 | } 242 | 243 | // If the exception doesn't implement the HttpExceptionInterface, we will just 244 | // use the generic 500 error code for a server side error. If it implements 245 | // the HttpException interfaces we'll grab the error code from the class. 246 | else 247 | { 248 | $code = 500; 249 | } 250 | 251 | // We will wrap this handler in a try / catch and avoid white screens of death 252 | // if any exceptions are thrown from a handler itself. This way we will get 253 | // at least some errors, and avoid errors with no data or not log writes. 254 | try 255 | { 256 | $response = $handler($exception, $code, $fromConsole); 257 | } 258 | catch (\Exception $e) 259 | { 260 | $response = $this->formatException($e); 261 | } 262 | 263 | // If this handler returns a "non-null" response, we will return it so it will 264 | // get sent back to the browsers. Once the handler returns a valid response 265 | // we will cease iterating through them and calling these other handlers. 266 | if (isset($response) && ! is_null($response)) 267 | { 268 | return $response; 269 | } 270 | } 271 | } 272 | 273 | /** 274 | * Display the given exception to the user. 275 | * 276 | * @param \Exception $exception 277 | * @return void 278 | */ 279 | protected function displayException($exception) 280 | { 281 | $displayer = $this->debug ? $this->debugDisplayer : $this->plainDisplayer; 282 | 283 | return $displayer->display($exception); 284 | } 285 | 286 | /** 287 | * Determine if the given handler handles this exception. 288 | * 289 | * @param \Closure $handler 290 | * @param \Exception $exception 291 | * @return bool 292 | */ 293 | protected function handlesException(Closure $handler, $exception) 294 | { 295 | $reflection = new ReflectionFunction($handler); 296 | 297 | return $reflection->getNumberOfParameters() == 0 || $this->hints($reflection, $exception); 298 | } 299 | 300 | /** 301 | * Determine if the given handler type hints the exception. 302 | * 303 | * @param \ReflectionFunction $reflection 304 | * @param \Exception $exception 305 | * @return bool 306 | */ 307 | protected function hints(ReflectionFunction $reflection, $exception) 308 | { 309 | $parameters = $reflection->getParameters(); 310 | 311 | $expected = $parameters[0]; 312 | 313 | return ! $expected->getClass() || $expected->getClass()->isInstance($exception); 314 | } 315 | 316 | /** 317 | * Format an exception thrown by a handler. 318 | * 319 | * @param \Exception $e 320 | * @return string 321 | */ 322 | protected function formatException(\Exception $e) 323 | { 324 | if ($this->debug) 325 | { 326 | $location = $e->getMessage().' in '.$e->getFile().':'.$e->getLine(); 327 | 328 | return 'Error in exception handler: '.$location; 329 | } 330 | 331 | return 'Error in exception handler.'; 332 | } 333 | 334 | /** 335 | * Register an application error handler. 336 | * 337 | * @param \Closure $callback 338 | * @return void 339 | */ 340 | public function error(Closure $callback) 341 | { 342 | array_unshift($this->handlers, $callback); 343 | } 344 | 345 | /** 346 | * Register an application error handler at the bottom of the stack. 347 | * 348 | * @param \Closure $callback 349 | * @return void 350 | */ 351 | public function pushError(Closure $callback) 352 | { 353 | $this->handlers[] = $callback; 354 | } 355 | 356 | /** 357 | * Register a 404 error handler. 358 | * 359 | * @param \Closure $callback 360 | * @return void 361 | */ 362 | public function missing(Closure $callback) 363 | { 364 | $this->error(function(NotFoundHttpException $e) use ($callback) 365 | { 366 | return call_user_func($callback, $e); 367 | }); 368 | } 369 | 370 | /** 371 | * Register an error handler for fatal errors. 372 | * 373 | * @param \Closure $callback 374 | * @return void 375 | */ 376 | public function fatal(Closure $callback) 377 | { 378 | $this->error(function(FatalError $e) use ($callback) 379 | { 380 | return call_user_func($callback, $e); 381 | }); 382 | } 383 | 384 | /** 385 | * Prepare the given response. 386 | * 387 | * @param mixed $response 388 | * @return \Illuminate\Http\Response 389 | */ 390 | protected function prepareResponse($response) 391 | { 392 | return $this->responsePreparer->prepareResponse($response); 393 | } 394 | 395 | /** 396 | * Determine if we are running in the console. 397 | * 398 | * @return bool 399 | */ 400 | public function runningInConsole() 401 | { 402 | return php_sapi_name() == 'cli'; 403 | } 404 | 405 | /** 406 | * Set the debug level for the handler. 407 | * 408 | * @param bool $debug 409 | * @return void 410 | */ 411 | public function setDebug($debug) 412 | { 413 | $this->debug = $debug; 414 | } 415 | 416 | } 417 | -------------------------------------------------------------------------------- /PlainDisplayer.php: -------------------------------------------------------------------------------- 1 | getStatusCode() : 500; 18 | 19 | $headers = $exception instanceof HttpExceptionInterface ? $exception->getHeaders() : array(); 20 | 21 | return new Response(file_get_contents(__DIR__.'/resources/plain.html'), $status, $headers); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /SymfonyDisplayer.php: -------------------------------------------------------------------------------- 1 | symfony = $symfony; 33 | $this->returnJson = $returnJson; 34 | } 35 | 36 | /** 37 | * Display the given exception to the user. 38 | * 39 | * @param \Exception $exception 40 | * @return \Symfony\Component\HttpFoundation\Response 41 | */ 42 | public function display(Exception $exception) 43 | { 44 | if ($this->returnJson) 45 | { 46 | return new JsonResponse(array( 47 | 'error' => $exception->getMessage(), 48 | 'file' => $exception->getFile(), 49 | 'line' => $exception->getLine(), 50 | ), 500); 51 | } 52 | 53 | return $this->symfony->createResponse($exception); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /WhoopsDisplayer.php: -------------------------------------------------------------------------------- 1 | whoops = $whoops; 34 | $this->runningInConsole = $runningInConsole; 35 | } 36 | 37 | /** 38 | * Display the given exception to the user. 39 | * 40 | * @param \Exception $exception 41 | * @return \Symfony\Component\HttpFoundation\Response 42 | */ 43 | public function display(Exception $exception) 44 | { 45 | $status = $exception instanceof HttpExceptionInterface ? $exception->getStatusCode() : 500; 46 | 47 | $headers = $exception instanceof HttpExceptionInterface ? $exception->getHeaders() : array(); 48 | 49 | return new Response($this->whoops->handleException($exception), $status, $headers); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "illuminate/exception", 3 | "license": "MIT", 4 | "authors": [ 5 | { 6 | "name": "Taylor Otwell", 7 | "email": "taylorotwell@gmail.com" 8 | } 9 | ], 10 | "require": { 11 | "php": ">=5.4.0", 12 | "filp/whoops": "1.1.*", 13 | "illuminate/support": "5.0.*", 14 | "symfony/debug": "2.6.*", 15 | "symfony/http-foundation": "2.6.*", 16 | "symfony/http-kernel": "2.6.*" 17 | }, 18 | "require-dev": { 19 | "monolog/monolog": "~1.6" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "Illuminate\\Exception\\": "" 24 | } 25 | }, 26 | "extra": { 27 | "branch-alias": { 28 | "dev-master": "5.0-dev" 29 | } 30 | }, 31 | "minimum-stability": "dev" 32 | } 33 | -------------------------------------------------------------------------------- /resources/plain.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 64 | 65 | 66 |