├── .gitignore ├── README.md ├── _config.yml ├── composer.json ├── license.md ├── resources └── lang │ └── en │ └── messages.php ├── screenshot.png └── src ├── Classes ├── DebugDisplay.php └── PlainDisplay.php ├── Contracts └── DisplayContract.php ├── Events └── ExceptionEvent.php ├── Handlers └── ExceptionHandler.php └── ServiceProvider.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Maintained: no](https://img.shields.io/maintenance/no/2017.svg?style=flat-square)](http://unmaintained.tech/) 2 | [![Latest Stable Version](https://img.shields.io/packagist/v/winternight/laravel-error-handler.svg?style=flat-square)](https://packagist.org/packages/winternight/laravel-error-handler) [![License](https://img.shields.io/dub/l/vibe-d.svg?style=flat-square)](license.md) [![Downloads](https://img.shields.io/packagist/dt/winternight/laravel-error-handler.svg?style=flat-square)](https://packagist.org/packages/winternight/laravel-error-handler) 3 | 4 | # ⚠️ No longer maintained 5 | 6 | Fortunately, [Laravel 5.5 has brought back the *Whoops* error handler](https://laravel-news.com/whoops-laravel-5-5) and this has continued throughout Laravel 5.6 and now 5.7. 7 | 8 | This repository and package can still be used for Laravel 5.2 and 5.3 and will continue to work. I will not add Laravel 5.4 support because [Laravel 5.4 itself is no longer supported](https://laravel.com/docs/5.7/releases#support-policy). 9 | 10 | Issue reports and pull requests on this repository will not be attended. 11 | 12 | # Laravel 5.2/5.3 Error Handler 13 | 14 | Unlike version 4, Laravel 5 no longer uses [Whoops](https://github.com/filp/whoops "filp/whoops") error handling out of 15 | the box and instead relies on the Symfony error handler, which is far less 16 | informative for developers. 17 | 18 | This packages provides a convenient way to get the more informative [Whoops](https://github.com/filp/whoops "filp/whoops") 19 | error messages back in your Laravel 5.2/5.3 project, along with a few other goodies. 20 | 21 | ![Exception](screenshot.png "The Whoops Error Handler in Action!" ) 22 | 23 | ## Features 24 | 25 | * Optional [Whoops](https://github.com/filp/whoops "filp/whoops") 1.1 or 2.1 exception handler in debug mode 26 | * Standard (and configurable) error views in production mode 27 | * Provides AJAX-compatible JSON error responses in case of an exception (including HTTP exceptions) 28 | * Fires an event for each exception, providing full access to the exception 29 | * Compatibility with [Laravel Debug Bar](https://github.com/barryvdh/laravel-debugbar "barryvdh/laravel-debugbar") 30 | 31 | ## Installation 32 | 33 | To get the latest version of Laravel Error Handler, simply require the project using Composer: 34 | 35 | ```bash 36 | $ composer require winternight/laravel-error-handler 37 | ``` 38 | 39 | Instead you can of course manually update the `require` block in your `composer.json` and add the `laravel-error-handler` package. 40 | 41 | ```json 42 | "require": { 43 | "winternight/laravel-error-handler": "^1" 44 | }, 45 | ``` 46 | 47 | ### Whoops 48 | 49 | Whoops itself is an optional dependency and you can do without Whoops on production. To install it, simply run: 50 | 51 | ```bash 52 | $ composer require filp/whoops --dev 53 | ``` 54 | 55 | Both Whoops `^1.1` and `^2.1` are supported but I strongly recommend you use the latest version, especially if you already use PHP 7. 56 | 57 | ## Configuration 58 | 59 | Add the service provider in your `config/app.php` : 60 | 61 | ```php 62 | ... 63 | Winternight\LaravelErrorHandler\ServiceProvider::class, 64 | ... 65 | ``` 66 | 67 | You then need to change your `App\Exceptions\Handler` class to extend `Winternight\LaravelErrorHandler\Handlers\ExceptionHandler` rather than extending `Illuminate\Foundation\Exceptions\Handler`. 68 | 69 | ```php 70 | 'errors.exception', 110 | ]; 111 | ``` 112 | 113 | ### HTTP Exceptions 114 | 115 | For HTTP exceptions like `404`, `403` and so on, the package will try to find a corresponding view. For example, to use 116 | a different view for `404` errors, you can simply create a `views/errors/404.blade.php` file and it will be used 117 | automatically. 118 | 119 | ### Maintenance Mode 120 | 121 | By default, Laravel [uses a custom view](https://laravel.com/docs/5.2/configuration#maintenance-mode) in maintenance mode 122 | to make it easy to disable your application while it's updating. The default template for maintenance mode responses is 123 | located in `views/errors/503.blade.php`. If you remove that file, this package still has you covered: Laravel's 124 | maintenance mode simply throws an exception with a `503` HTTP status code, so you will be shown the default error view 125 | when your application's debug mode is off. With debug mode turned on, you will be shown the actual exception with the 126 | Whoops error handler. 127 | 128 | ## Events 129 | 130 | Whenever an error occurs, an `ExceptionEvent` is raised. You can use your `EventServiceProvider` to listen to this type 131 | of event if you wish: 132 | 133 | 134 | ```php 135 | protected $listen = [ 136 | 'Winternight\LaravelErrorHandler\Events\ExceptionEvent' => [ 137 | 'App\Listeners\ExceptionEventListener', 138 | ], 139 | ]; 140 | ``` 141 | 142 | ## Is this compatible with [Laravel Debug Bar](https://github.com/barryvdh/laravel-debugbar "barryvdh/laravel-debugbar")? 143 | 144 | I'm glad you asked! Yes, it is. The debug bar simply renders on top of the Whoops error page. You can also use the Event 145 | that is being fired whenever an exception occurs to add it to your debug bar. Here's a small example which you can put 146 | in your `EventServiceProvider`'s `boot()` method: 147 | 148 | ```php 149 | \Event::listen( 'Winternight\LaravelErrorHandler\Events\ExceptionEvent', function ( $event ) { 150 | \Debugbar::addException( $event->getException() ); 151 | } ); 152 | ``` 153 | 154 | ## Troubleshooting 155 | 156 | If you're not seeing any difference (and you have made sure there actually is an error being thrown, you probably have 157 | debugging disabled. That means that Laravel will (and should) not disclose any details regarding the error that has 158 | occurred. It's for your own protection. 159 | 160 | To enable Whoops, open up your `config/app.php` configuration file, find the `debug` setting and change it to `true`. As 161 | soon as you encounter an error, you will see the Whoops error handler. If you have done everything right, you should 162 | probably use the `.env` file to set the `APP_DEBUG` environment variable. 163 | 164 | ## What about AJAX requests? 165 | 166 | Whenever an AJAX request triggers the error handler, it will be recognized and the so called `PrettyPageHandler` will be 167 | exchanged for a `JsonResponseHandler` that returns a JSON response that you can parse on the client side. You can read 168 | [more here](https://github.com/filp/whoops/blob/master/docs/API%20Documentation.md#-whoopshandlerjsonresponsehandler). 169 | If debug mode is turned off, a different JSON object will be returned instead that still allows you to gracefully handle 170 | your AJAX errors, while not giving out any information about your code. 171 | 172 | Here is a (very) simple jQuery snippet for global AJAX error handling: 173 | 174 | ```js 175 | $( document ).ajaxError(function( evt, xhr ) { 176 | console.log( xhr.responseJSON.error ); 177 | } ); 178 | ``` 179 | 180 | The JSON object will always contain an `error` property which in turn will always at least contain the `type` and `message` 181 | properties. If debug mode is enabled, it will additionally contain the `file` and `line` properties and — in case of an 182 | Exception — also the `trace` property. The `trace` property contains the full exception stack trace. 183 | 184 | ## What about errors in the console? 185 | 186 | The package is smart enough not to mess with output to the console. Enjoy your errors in plain text! 187 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "darkwinternight/laravel-error-handler", 3 | "description": "Error handler package for the Laravel 5 framework", 4 | "homepage": "https://github.com/darkwinternight/laravel-error-handler", 5 | "keywords": [ "errorhandler", "error", "exception", "debug", "laravel", "laravel 5", "whoops", "ajax" ], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Alexander Graf", 10 | "email": "a.graf@aetherworld.org" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.6.0", 15 | "illuminate/contracts": "5.2.*|5.3.*", 16 | "illuminate/support": "5.2.*|5.3.*", 17 | "symfony/debug": "^2.7|^3.0", 18 | "symfony/http-foundation": "^2.7|^3.0", 19 | "psr/log": "^1.0" 20 | }, 21 | "require-dev": { 22 | "filp/whoops": "^2.1" 23 | }, 24 | "suggest": { 25 | "filp/whoops": "Enables use of the debug displayer." 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Winternight\\LaravelErrorHandler\\": "src/" 30 | } 31 | }, 32 | "extra": { 33 | "branch-alias": { 34 | "dev-master": "1.x-dev" 35 | } 36 | }, 37 | "config": { 38 | "preferred-install": "dist" 39 | }, 40 | "minimum-stability": "dev", 41 | "prefer-stable": true 42 | } -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Alexander Graf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /resources/lang/en/messages.php: -------------------------------------------------------------------------------- 1 | [ 5 | '400' => [ 6 | 'name' => 'Bad Request', 7 | 'message' => 'The request cannot be fulfilled due to bad syntax.', 8 | ], 9 | '401' => [ 10 | 'name' => 'Unauthorized', 11 | 'message' => 'Authentication is required and has failed or has not yet been provided.', 12 | ], 13 | '403' => [ 14 | 'name' => 'Forbidden', 15 | 'message' => 'You do not have permission to perform that request.', 16 | ], 17 | '404' => [ 18 | 'name' => 'Not Found', 19 | 'message' => 'The requested resource could not be found.', 20 | ], 21 | '405' => [ 22 | 'name' => 'Method Not Allowed', 23 | 'message' => 'A request was made of a resource using a request method not supported by that resource.', 24 | ], 25 | '406' => [ 26 | 'name' => 'Not Acceptable', 27 | 'message' => 'The requested resource is only capable of generating content not acceptable.', 28 | ], 29 | '409' => [ 30 | 'name' => 'Conflict', 31 | 'message' => 'The request could not be processed because of a conflict in the request.', 32 | ], 33 | '410' => [ 34 | 'name' => 'Gone', 35 | 'message' => 'The requested resource is no longer available and will not be available again.', 36 | ], 37 | '411' => [ 38 | 'name' => 'Length Required', 39 | 'message' => 'The request did not specify the length of its content, which is required by the requested resource.', 40 | ], 41 | '412' => [ 42 | 'name' => 'Precondition Failed', 43 | 'message' => 'The server does not meet one of the preconditions that the requester put on the request.', 44 | ], 45 | '415' => [ 46 | 'name' => 'Unsupported Media Type', 47 | 'message' => 'The request entity has a media type which the server or resource does not support.', 48 | ], 49 | '422' => [ 50 | 'name' => 'Unprocessable Entity', 51 | 'message' => 'The request was well-formed but was unable to be followed due to semantic errors.', 52 | ], 53 | '428' => [ 54 | 'name' => 'Precondition Required', 55 | 'message' => 'The origin server requires the request to be conditional.', 56 | ], 57 | '429' => [ 58 | 'name' => 'Too Many Requests', 59 | 'message' => 'The user has sent too many requests in a given amount of time.', 60 | ], 61 | '500' => [ 62 | 'name' => 'Internal Server Error', 63 | 'message' => 'An error has occurred and this resource cannot be displayed.', 64 | ], 65 | '501' => [ 66 | 'name' => 'Not Implemented', 67 | 'message' => 'The server either does not recognize the request method, or it lacks the ability to fulfil the request.', 68 | ], 69 | '502' => [ 70 | 'name' => 'Bad Gateway', 71 | 'message' => 'The server was acting as a gateway or proxy and received an invalid response from the upstream server.', 72 | ], 73 | '503' => [ 74 | 'name' => 'Service Unavailable', 75 | 'message' => 'The server is currently unavailable. It may be overloaded or down for maintenance.', 76 | ], 77 | '504' => [ 78 | 'name' => 'Gateway Timeout', 79 | 'message' => 'The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.', 80 | ], 81 | '505' => [ 82 | 'name' => 'HTTP Version Not Supported', 83 | 'message' => 'The server does not support the HTTP protocol version used in the request.', 84 | ] 85 | ] 86 | ]; 87 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otherguy/laravel-error-handler/796408e4f50e638411a3ecb5f4e7c030f88ba23e/screenshot.png -------------------------------------------------------------------------------- /src/Classes/DebugDisplay.php: -------------------------------------------------------------------------------- 1 | whoops()->handleException($exception); 38 | if( $this->request->wantsJson() ) { 39 | return Response::create( $content, $code )->header( 'Content-Type', 'application/json'); 40 | } else { 41 | return Response::create( $content, $code); 42 | } 43 | } 44 | 45 | /** 46 | * Set the HTTP Request instance. 47 | * 48 | * @param \Illuminate\Http\Request $request 49 | * 50 | * @return $this 51 | */ 52 | public function setRequest(Request $request) 53 | { 54 | $this->request = $request; 55 | return $this; 56 | } 57 | 58 | /** 59 | * Get the whoops instance. 60 | * 61 | * @return \Whoops\Run 62 | */ 63 | protected function whoops() 64 | { 65 | // Default to the Whoops HTML Handler 66 | $handler = new WhoopsHtmlHandler(); 67 | 68 | // For JSON or XML Requests, return the proper output too. 69 | if ($this->request instanceof Request && $this->request->wantsJson() ) { 70 | $handler = new WhoopsJsonHandler(); 71 | $handler->addTraceToOutput(true); 72 | } 73 | 74 | // Build Whoops. 75 | $whoops = new Whoops(); 76 | $whoops->allowQuit(false); 77 | $whoops->writeToOutput(false); 78 | $whoops->pushHandler($handler); 79 | $whoops->register(); 80 | 81 | // Return Whoops. 82 | return $whoops; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Classes/PlainDisplay.php: -------------------------------------------------------------------------------- 1 | config = $config; 46 | $this->view = $view; 47 | $this->lang = $lang; 48 | } 49 | 50 | /** 51 | * Set the HTTP Request instance. 52 | * 53 | * @param \Illuminate\Http\Request $request 54 | * 55 | * @return $this 56 | */ 57 | public function setRequest(Request $request) 58 | { 59 | $this->request = $request; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * Get the HTML content associated with the given exception. 66 | * 67 | * @param \Exception $exception 68 | * @param int $code 69 | * 70 | * @return string 71 | */ 72 | public function display(Exception $exception, $code) 73 | { 74 | // Collect some info about the exception. 75 | $info = $this->info($code, $exception); 76 | 77 | // Is the current request an AJAX request? 78 | if ((boolean)($this->request instanceof Request && $this->request->wantsJson()) == true) { 79 | $json_object = (object)[ 'error' => [ 80 | 'type' => 'Exception', 81 | 'message' => $info['message'], 82 | ] ]; 83 | 84 | return JsonResponse::create($json_object, $code); 85 | } 86 | 87 | // For model-not-found, use 404 errors. 88 | if ($exception instanceof ModelNotFoundException) { 89 | $code = 404; 90 | } 91 | 92 | // If there is a custom view, use that. 93 | if ($this->view->exists("errors.{$code}")) { 94 | return $this->view->make("errors.{$code}", $info)->render(); 95 | } 96 | 97 | // If the configured default error view (or errors.error) exists, render that. 98 | if (($errorview = $this->config->get('view.error', 'errors.error')) && $this->view->exists($errorview)) { 99 | return $this->view->make($errorview, $info)->render(); 100 | } 101 | 102 | // Last resort: simply show the error code and message. 103 | return new Response($this->getFallbackDisplay($code, $info['name'], $info['message']), $code); 104 | } 105 | 106 | /** 107 | * Get the exception information. 108 | * 109 | * @param int $code 110 | * @param \Exception $exception 111 | * 112 | * @return array 113 | */ 114 | protected function info($code, Exception $exception) 115 | { 116 | $description = $exception->getMessage(); 117 | $namespace = 'winternight/laravel-error-handler'; 118 | 119 | // If there is no error message for the given HTTP code, default to 500. 120 | if (!$this->lang->has("$namespace::messages.error.$code.name")) { 121 | $code = 500; 122 | } 123 | 124 | $name = $this->lang->get("$namespace::messages.error.$code.name"); 125 | $message = $this->lang->get("$namespace::messages.error.$code.message"); 126 | 127 | return compact('code', 'name', 'message', 'description'); 128 | } 129 | 130 | /** 131 | * Get the fallback html response content. 132 | * 133 | * @param string $code 134 | * @param string $name 135 | * @param string $message 136 | * 137 | * @return string 138 | */ 139 | protected function getFallbackDisplay($code, $name, $message) 140 | { 141 | return << 143 | 144 | 145 | 146 | 147 | 148 |

$code $name

149 |

$message

150 | 151 | 152 | EOF; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/Contracts/DisplayContract.php: -------------------------------------------------------------------------------- 1 | exception = $exception; 28 | } 29 | 30 | /** 31 | * Returns the exception instance. 32 | * 33 | * @return Exception 34 | */ 35 | public function getException() 36 | { 37 | return $this->exception; 38 | } 39 | 40 | /** 41 | * Get the channels the event should be broadcast on. 42 | * 43 | * @return array 44 | */ 45 | public function broadcastOn() 46 | { 47 | return []; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Handlers/ExceptionHandler.php: -------------------------------------------------------------------------------- 1 | config = $container->config; 46 | $this->app = $container->app; 47 | $this->event = $container->events; 48 | $this->container = $container; 49 | 50 | parent::__construct($container); 51 | } 52 | 53 | /** 54 | * Report or log an exception. 55 | * 56 | * This is a great spot to send exceptions to Sentry, Bugsnag, etc. 57 | * In this implementation, we just send an Event though. 58 | * 59 | * @param \Exception $e 60 | * 61 | * @return void 62 | */ 63 | public function report(Exception $e) 64 | { 65 | if ($this->shouldReport($e)) { 66 | $this->event->fire(new ExceptionEvent($e)); 67 | } 68 | parent::report($e); 69 | } 70 | 71 | /** 72 | * Render an exception into an HTTP response. 73 | * 74 | * @param \Illuminate\Http\Request $request 75 | * @param \Exception $exception 76 | * 77 | * @return Response|BaseResponse 78 | */ 79 | public function render($request, Exception $exception) 80 | { 81 | $exception = $this->prepareException($exception); 82 | 83 | if ($exception instanceof HttpResponseException) { 84 | return $exception->getResponse(); 85 | } elseif ($exception instanceof AuthenticationException) { 86 | return $this->unauthenticated($request, $exception); 87 | } elseif ($exception instanceof ValidationException) { 88 | return $this->convertValidationExceptionToResponse($exception, $request); 89 | } elseif ($this->isHttpException($exception) && $this->customErrorHtmlTemplateExists($exception)) { 90 | return $this->prepareResponse($request, $exception); 91 | } 92 | 93 | $flattened = FlattenException::create($exception); 94 | 95 | $code = $flattened->getStatusCode(); 96 | $headers = $flattened->getHeaders(); 97 | $response = $this->getContent($exception, $code, $request); 98 | 99 | // If it's already a response, return that. 100 | if (!$response instanceof BaseResponse) { 101 | $response = new Response($this->getContent($exception, $code, $request), $code, $headers); 102 | } 103 | 104 | return $response; 105 | } 106 | 107 | /** 108 | * Get the HTML content associated with the given exception. 109 | * 110 | * @param \Exception $exception 111 | * @param int $code 112 | * @param \Illuminate\Http\Request $request 113 | * 114 | * @return string 115 | */ 116 | protected function getContent(Exception $exception, $code, Request $request) 117 | { 118 | 119 | // Only if the debug mode is enabled, show a more verbose error message. 120 | if ((boolean)$this->config->get('app.debug') === true) { 121 | if (class_exists(Whoops::class)) { 122 | // If Whoops is loaded, use the DebugDisplay class. 123 | return $this->app->make(DebugDisplay::class)->setRequest($request)->display($exception, $code); 124 | } else { 125 | // Fall back to the default Laravel error response. 126 | return $this->toIlluminateResponse($this->convertExceptionToResponse($exception), $exception); 127 | } 128 | } 129 | 130 | // For production/non-debug environments, use the PlainDisplay class. 131 | return $this->app->make(PlainDisplay::class)->setRequest($request)->display($exception, $code); 132 | } 133 | 134 | /** 135 | * Convert an authentication exception into an unauthenticated response. 136 | * 137 | * @param \Illuminate\Http\Request $request 138 | * @param \Illuminate\Auth\AuthenticationException $exception 139 | * 140 | * @return \Illuminate\Http\Response 141 | */ 142 | protected function unauthenticated($request, AuthenticationException $exception) 143 | { 144 | if ($request->expectsJson()) { 145 | return response()->json(['error' => 'Unauthenticated.'], 401); 146 | } 147 | 148 | return redirect()->guest('login'); 149 | } 150 | 151 | /** 152 | * Check if custom html template exists for given exception status code 153 | * 154 | * @param HttpException $exception 155 | * 156 | * @return bool 157 | */ 158 | protected function customErrorHtmlTemplateExists(HttpException $exception) 159 | { 160 | $statusCode = $exception->getStatusCode(); 161 | 162 | return view()->exists("errors/$statusCode"); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadTranslationsFrom(__DIR__ . '/../../../resources/lang/', $this->namespace); 32 | } 33 | 34 | /** 35 | * Get the services provided by the provider. 36 | * 37 | * @return array 38 | */ 39 | public function provides() 40 | { 41 | return ['Illuminate\Contracts\Debug\ExceptionHandler']; 42 | } 43 | 44 | /** 45 | * Register the service provider. 46 | * 47 | * @return void 48 | */ 49 | public function register() 50 | { 51 | } 52 | } 53 | --------------------------------------------------------------------------------