├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── HandlerDecorator.php ├── HandlersRepository.php └── Providers └── ExceptionHandlerServiceProvider.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `exception-handler` will be documented in this file. 4 | 5 | Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles. 6 | 7 | 8 | 9 | ## 3.0.0 - 2020-08-27 10 | 11 | ### Added 12 | - Support for Laravel 7. 13 | 14 | ### Removed 15 | - Support for previous versions of Laravel due to the framework breaking changes. 16 | - Support for versions of PHP prior to 7.2. 17 | 18 | 19 | ## 2.0.1 - 2019-09-29 20 | 21 | ### Fixed 22 | - Add missing caret symbol to required packages version. 23 | 24 | 25 | ## 2.0.0 - 2019-09-28 26 | 27 | ### Added 28 | - Support for Laravel 6. 29 | - Complete refactor of the package. 30 | 31 | ### Removed 32 | - Exceptions facade. 33 | 34 | 35 | ## 1.0.0 - 2016-04-17 36 | 37 | ### Added 38 | - Decorator for the Laravel default exception handler to register custom handlers via service providers. 39 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at `andrea.marco.sartori@gmail.com`. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/cerbero90/exception-handler). 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - Check the code style with ``$ composer check-style`` and fix it with ``$ composer fix-style``. 11 | 12 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 13 | 14 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 15 | 16 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 17 | 18 | - **Create feature branches** - Don't ask us to pull from your master branch. 19 | 20 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 21 | 22 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 23 | 24 | 25 | ## Running Tests 26 | 27 | ``` bash 28 | $ composer test 29 | ``` 30 | 31 | 32 | **Happy coding**! 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Andrea Marco Sartori 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exception Handler 2 | 3 | [![Author][ico-author]][link-author] 4 | [![Latest Version on Packagist][ico-version]][link-packagist] 5 | [![Software License][ico-license]](LICENSE.md) 6 | [![Build Status][ico-travis]][link-travis] 7 | [![Coverage Status][ico-scrutinizer]][link-scrutinizer] 8 | [![Quality Score][ico-code-quality]][link-code-quality] 9 | [![Total Downloads][ico-downloads]][link-downloads] 10 | 11 | [![SensioLabsInsight][ico-sensiolabs]][link-sensiolabs] 12 | 13 | This Laravel package lets you define the behavior of your application when a specific exception is thrown. 14 | 15 | Laravel handles exceptions in `app/Exceptions/Handler.php` by default, but the more handlers you add the more this class gets cluttered and difficult to read and maintain. 16 | 17 | Furthermore it is not possible for an external Laravel package to automatically register how its custom exceptions should be handled by the application where it has been installed. 18 | 19 | This package lets you register custom exception handlers by using service providers, so that also external packages may register their own. 20 | 21 | > **Please note:** this package leverages the decorators design pattern, which allows you to keep using the Laravel default handler as you normally would. It just wraps the exception handler to extend its functionalities. 22 | 23 | ## Install 24 | 25 | Via Composer 26 | 27 | ``` bash 28 | composer require cerbero/exception-handler 29 | ``` 30 | 31 | If your Laravel version is prior to 5.5, please add this service provider in `config/app.php` 32 | 33 | ``` php 34 | 'providers' => [ 35 | ... 36 | Cerbero\ExceptionHandler\Providers\ExceptionHandlerServiceProvider::class, 37 | ] 38 | ``` 39 | 40 | ## Usage 41 | 42 | There are 3 types of handlers that can be registered: 43 | 44 | - **Reporters**, they log an exception or report it to an external service (e.g. Sentry, Bugsnag...) 45 | - **Renderers**, they render an exception into an HTTP response 46 | - **Console Renderers**, they render an exception to the console 47 | 48 | The following examples show how to register each type of handler in the `boot()` method of a service provider: 49 | 50 | ``` php 51 | use App\Exceptions\DebugException; 52 | use App\Exceptions\ArtisanException; 53 | use Illuminate\Contracts\Debug\ExceptionHandler; 54 | use Illuminate\Contracts\Validation\ValidationException; 55 | 56 | ... 57 | 58 | public function boot() 59 | { 60 | // register a custom reporter to log all exceptions that are instances of - or extend - DebugException 61 | $this->app->make(ExceptionHandler::class)->reporter(function (DebugException $e) { 62 | $this->app['log']->debug($e->getMessage()); 63 | }); 64 | 65 | // register a custom renderer to redirect the user back and show validation errors 66 | $this->app->make(ExceptionHandler::class)->renderer(function (ValidationException $e, $request) { 67 | return back()->withInput()->withErrors($e->errors()); 68 | }); 69 | 70 | // register a custom console renderer to display errors to the console and stop the propagation of other exceptions 71 | $this->app->make(ExceptionHandler::class)->consoleRenderer(function (ArtisanException $e, $output) { 72 | $output->writeln('' . $e->getMessage() . ''); 73 | return true; 74 | }); 75 | } 76 | ``` 77 | 78 | A handler is basically a callback that accepts the exception to handle as first parameter. You can register a global handler by omitting the exception type or type-hinting `Exception`. 79 | 80 | Unlike reporters, renderers also accept a second parameter: an instance of `Illuminate\Http\Request` in case of a renderer or a `Symfony\Component\Console\Output\OutputInterface` in case of a console renderer. 81 | 82 | It is also important to note that all registered handlers for an exception will be called until one of them returns a truthy value, in that case the exceptions propagation stops. 83 | 84 | ## Change log 85 | 86 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 87 | 88 | ## Testing 89 | 90 | ``` bash 91 | composer test 92 | ``` 93 | 94 | ## Contributing 95 | 96 | Please see [CONTRIBUTING](CONTRIBUTING.md) and [CONDUCT](CONDUCT.md) for details. 97 | 98 | ## Security 99 | 100 | If you discover any security related issues, please email andrea.marco.sartori@gmail.com instead of using the issue tracker. 101 | 102 | ## Credits 103 | 104 | - [Andrea Marco Sartori][link-author] 105 | - [All Contributors][link-contributors] 106 | 107 | ## License 108 | 109 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 110 | 111 | [ico-author]: http://img.shields.io/badge/author-@cerbero90-blue.svg?style=flat-square 112 | [ico-version]: https://img.shields.io/packagist/v/cerbero/exception-handler.svg?style=flat-square 113 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square 114 | [ico-travis]: https://img.shields.io/travis/cerbero90/exception-handler/master.svg?style=flat-square 115 | [ico-scrutinizer]: https://img.shields.io/scrutinizer/coverage/g/cerbero90/exception-handler.svg?style=flat-square 116 | [ico-code-quality]: https://img.shields.io/scrutinizer/g/cerbero90/exception-handler.svg?style=flat-square 117 | [ico-downloads]: https://img.shields.io/packagist/dt/cerbero/exception-handler.svg?style=flat-square 118 | [ico-sensiolabs]: https://insight.sensiolabs.com/projects/696d2f5d-ea7e-484a-8e06-836dcb462b19/big.png 119 | 120 | [link-author]: https://twitter.com/cerbero90 121 | [link-packagist]: https://packagist.org/packages/cerbero/exception-handler 122 | [link-travis]: https://travis-ci.org/cerbero90/exception-handler 123 | [link-scrutinizer]: https://scrutinizer-ci.com/g/cerbero90/exception-handler/code-structure 124 | [link-code-quality]: https://scrutinizer-ci.com/g/cerbero90/exception-handler 125 | [link-downloads]: https://packagist.org/packages/cerbero/exception-handler 126 | [link-sensiolabs]: https://insight.sensiolabs.com/projects/696d2f5d-ea7e-484a-8e06-836dcb462b19 127 | [link-contributors]: ../../contributors 128 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cerbero/exception-handler", 3 | "type": "library", 4 | "description": "Extend Laravel exception handler to define how to handle custom exceptions via service providers.", 5 | "keywords": [ 6 | "laravel", 7 | "exception-handler", 8 | "exceptions" 9 | ], 10 | "homepage": "https://github.com/cerbero90/exception-handler", 11 | "license": "MIT", 12 | "authors": [{ 13 | "name": "Andrea Marco Sartori", 14 | "email": "andrea.marco.sartori@gmail.com", 15 | "homepage": "https://github.com/cerbero90", 16 | "role": "Developer" 17 | }], 18 | "require": { 19 | "php": "^7.2", 20 | "illuminate/support": "^7.0", 21 | "illuminate/contracts": "^7.0" 22 | }, 23 | "require-dev": { 24 | "orchestra/testbench": "^5.0", 25 | "phpunit/phpunit": "^8.0|^9.0", 26 | "mockery/mockery": "^1.0", 27 | "squizlabs/php_codesniffer": "^3.0" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "Cerbero\\ExceptionHandler\\": "src" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "Cerbero\\ExceptionHandler\\": "tests" 37 | } 38 | }, 39 | "scripts": { 40 | "test": "phpunit", 41 | "check-style": "phpcs src tests", 42 | "fix-style": "phpcbf src tests" 43 | }, 44 | "extra": { 45 | "branch-alias": { 46 | "dev-master": "1.0-dev" 47 | }, 48 | "laravel": { 49 | "providers": [ 50 | "Cerbero\\ExceptionHandler\\Providers\\ExceptionHandlerServiceProvider" 51 | ] 52 | } 53 | }, 54 | "config": { 55 | "sort-packages": true 56 | } 57 | } -------------------------------------------------------------------------------- /src/HandlerDecorator.php: -------------------------------------------------------------------------------- 1 | defaultHandler = $defaultHandler; 37 | 38 | $this->repository = $repository; 39 | } 40 | 41 | /** 42 | * Report or log an exception. 43 | * 44 | * @param \Throwable $e 45 | * @return mixed 46 | * 47 | * @throws \Throwable 48 | */ 49 | public function report(Throwable $e) 50 | { 51 | foreach ($this->repository->getReportersByException($e) as $reporter) { 52 | if ($report = $reporter($e)) { 53 | return $report; 54 | } 55 | } 56 | 57 | $this->defaultHandler->report($e); 58 | } 59 | 60 | /** 61 | * Register a custom handler to report exceptions 62 | * 63 | * @param callable $callback 64 | * @return int 65 | */ 66 | public function reporter(callable $reporter) 67 | { 68 | return $this->repository->addReporter($reporter); 69 | } 70 | 71 | /** 72 | * Render an exception into a response. 73 | * 74 | * @param \Illuminate\Http\Request $request 75 | * @param \Throwable $e 76 | * @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response 77 | */ 78 | public function render($request, Throwable $e) 79 | { 80 | foreach ($this->repository->getRenderersByException($e) as $renderer) { 81 | if ($render = $renderer($e, $request)) { 82 | return $render; 83 | } 84 | } 85 | 86 | return $this->defaultHandler->render($request, $e); 87 | } 88 | 89 | /** 90 | * Register a custom handler to render exceptions 91 | * 92 | * @param callable $callback 93 | * @return int 94 | */ 95 | public function renderer(callable $renderer) 96 | { 97 | return $this->repository->addRenderer($renderer); 98 | } 99 | 100 | /** 101 | * Render an exception to the console. 102 | * 103 | * @param \Symfony\Component\Console\Output\OutputInterface $output 104 | * @param \Throwable $e 105 | * @return mixed 106 | */ 107 | public function renderForConsole($output, Throwable $e) 108 | { 109 | foreach ($this->repository->getConsoleRenderersByException($e) as $renderer) { 110 | if ($render = $renderer($e, $output)) { 111 | return $render; 112 | } 113 | } 114 | 115 | $this->defaultHandler->renderForConsole($output, $e); 116 | } 117 | 118 | /** 119 | * Register a custom handler to render exceptions in console 120 | * 121 | * @param callable $callback 122 | * @return int 123 | */ 124 | public function consoleRenderer(callable $renderer) 125 | { 126 | return $this->repository->addConsoleRenderer($renderer); 127 | } 128 | 129 | /** 130 | * Determine if the exception should be reported. 131 | * 132 | * @param \Throwable $e 133 | * @return bool 134 | */ 135 | public function shouldReport(Throwable $e) 136 | { 137 | return $this->defaultHandler->shouldReport($e); 138 | } 139 | 140 | /** 141 | * Proxy other calls to default Laravel exception handler 142 | * 143 | * @param string $name 144 | * @param array $parameters 145 | * @return mixed 146 | */ 147 | public function __call($name, $parameters) 148 | { 149 | return call_user_func_array([$this->defaultHandler, $name], $parameters); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/HandlersRepository.php: -------------------------------------------------------------------------------- 1 | reporters, $reporter); 44 | } 45 | 46 | /** 47 | * Register a custom handler to render exceptions 48 | * 49 | * @param callable $renderer 50 | * @return int 51 | */ 52 | public function addRenderer(callable $renderer) 53 | { 54 | return array_unshift($this->renderers, $renderer); 55 | } 56 | 57 | /** 58 | * Register a custom handler to render exceptions in console 59 | * 60 | * @param callable $renderer 61 | * @return int 62 | */ 63 | public function addConsoleRenderer(callable $renderer) 64 | { 65 | return array_unshift($this->consoleRenderers, $renderer); 66 | } 67 | 68 | /** 69 | * Retrieve all reporters handling the given exception 70 | * 71 | * @param \Throwable $e 72 | * @return array 73 | */ 74 | public function getReportersByException(Throwable $e) 75 | { 76 | return array_filter($this->reporters, function (callable $handler) use ($e) { 77 | return $this->handlesException($handler, $e); 78 | }); 79 | } 80 | 81 | /** 82 | * Determine whether the given handler can handle the provided exception 83 | * 84 | * @param callable $handler 85 | * @param \Throwable $e 86 | * @return bool 87 | */ 88 | protected function handlesException(callable $handler, Throwable $e) 89 | { 90 | $reflection = new ReflectionFunction($handler); 91 | 92 | if (!$params = $reflection->getParameters()) { 93 | return false; 94 | } 95 | 96 | return $params[0]->getClass() ? $params[0]->getClass()->isInstance($e) : true; 97 | } 98 | 99 | /** 100 | * Retrieve all renderers handling the given exception 101 | * 102 | * @param \Throwable $e 103 | * @return array 104 | */ 105 | public function getRenderersByException(Throwable $e) 106 | { 107 | return array_filter($this->renderers, function (callable $handler) use ($e) { 108 | return $this->handlesException($handler, $e); 109 | }); 110 | } 111 | 112 | /** 113 | * Retrieve all console renderers handling the given exception 114 | * 115 | * @param \Throwable $e 116 | * @return array 117 | */ 118 | public function getConsoleRenderersByException(Throwable $e) 119 | { 120 | return array_filter($this->consoleRenderers, function (callable $handler) use ($e) { 121 | return $this->handlesException($handler, $e); 122 | }); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Providers/ExceptionHandlerServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerExceptionHandlersRepository(); 24 | 25 | $this->extendExceptionHandler(); 26 | } 27 | 28 | /** 29 | * Register the custom exception handlers repository. 30 | * 31 | * @return void 32 | */ 33 | private function registerExceptionHandlersRepository() 34 | { 35 | $this->app->singleton(HandlersRepository::class, HandlersRepository::class); 36 | } 37 | 38 | /** 39 | * Extend the Laravel default exception handler. 40 | * 41 | * @return void 42 | */ 43 | private function extendExceptionHandler() 44 | { 45 | $this->app->extend(ExceptionHandler::class, function (ExceptionHandler $handler, $app) { 46 | return new HandlerDecorator($handler, $app[HandlersRepository::class]); 47 | }); 48 | } 49 | } 50 | --------------------------------------------------------------------------------