├── .editorconfig ├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── composer.json ├── example.php ├── phpunit.xml.dist ├── src ├── Cake │ ├── ConsoleErrorHandler.php │ └── ErrorHandler.php ├── Exception │ ├── FatalErrorException.php │ └── PHP7ErrorException.php ├── Handler.php ├── Handler │ ├── AbstractHandler.php │ ├── AirbrakeHandler.php │ ├── AtatusHandler.php │ ├── BugsnagHandler.php │ ├── HandlerInterface.php │ ├── MonologStreamHandler.php │ ├── NewrelicHandler.php │ ├── RaygunHandler.php │ └── SentryHandler.php └── Utility │ └── ConfigTrait.php └── tests └── bootstrap.php /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = false 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | charset = "utf-8" 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.yml] 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | tmp/ 4 | build/ 5 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - php 3 | 4 | filter: 5 | excluded_paths: 6 | - docs/ 7 | - tests/ 8 | tools: 9 | php_mess_detector: true 10 | php_cpd: 11 | excluded_dirs: 12 | - docs/ 13 | - tests/ 14 | php_loc: 15 | excluded_dirs: 16 | - docs/ 17 | - tests/ 18 | php_pdepend: 19 | excluded_dirs: 20 | 1: docs/ 21 | 2: tests/ 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.0 5 | - 7.1 6 | 7 | sudo: false 8 | 9 | env: 10 | global: 11 | - DEFAULT=1 12 | 13 | matrix: 14 | fast_finish: true 15 | 16 | include: 17 | - php: 7.0 18 | env: PHPCS=1 DEFAULT=0 19 | 20 | - php: 7.0 21 | env: COVERALLS=1 DEFAULT=0 22 | 23 | before_script: 24 | - composer self-update 25 | - composer install --prefer-source --no-interaction --dev 26 | - sh -c "if [ '$COVERALLS' = '1' ]; then mkdir -p build/logs; fi" 27 | 28 | - command -v phpenv > /dev/null && phpenv rehash || true 29 | 30 | script: 31 | - sh -c "if [ '$COVERALLS' = '1' ]; then phpunit --stderr --coverage-clover build/logs/clover.xml; fi" 32 | - sh -c "if [ '$COVERALLS' = '1' ]; then php vendor/bin/php-coveralls -c .coveralls.yml -v; fi" 33 | - sh -c "if [ '$DEFAULT' = '1' ]; then vendor/bin/phpunit --stderr; fi" 34 | - sh -c "if [ '$PHPCS' = '1' ]; then vendor/bin/phpcs -n -p --extensions=php --standard=vendor/cakephp/cakephp-codesniffer/CakePHP --ignore=vendor --ignore=docs --ignore=tests/bootstrap.php . ; fi" 35 | 36 | notifications: 37 | email: false 38 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | ErrorHandlers loves to welcome your contributions. There are several ways to help out: 4 | * Create a ticket in GitHub, if you have found a bug 5 | * Write testcases for open bug tickets 6 | * Write patches for open bug/feature tickets, preferably with testcases included 7 | * Contribute to the [documentation](https://github.com/josegonzalez/cakephp-error-handlers/tree/gh-pages) 8 | 9 | There are a few guidelines that we need contributors to follow so that we have a 10 | chance of keeping on top of things. 11 | 12 | ## Getting Started 13 | 14 | * Make sure you have a [GitHub account](https://github.com/signup/free) 15 | * Submit a ticket for your issue, assuming one does not already exist. 16 | * Clearly describe the issue including steps to reproduce when it is a bug. 17 | * Make sure you fill in the earliest version that you know has the issue. 18 | * Fork the repository on GitHub. 19 | 20 | ## Making Changes 21 | 22 | * Create a topic branch from where you want to base your work. 23 | * This is usually the develop branch 24 | * To quickly create a topic branch based on master; `git branch 25 | master/my_contribution master` then checkout the new branch with `git 26 | checkout master/my_contribution`. Better avoid working directly on the 27 | `master` branch, to avoid conflicts if you pull in updates from origin. 28 | * Make commits of logical units. 29 | * Check for unnecessary whitespace with `git diff --check` before committing. 30 | * Use descriptive commit messages and reference the #ticket number 31 | * Core testcases should continue to pass. You can run tests locally or enable 32 | [travis-ci](https://travis-ci.org/) for your fork, so all tests and codesniffs 33 | will be executed. 34 | * Your work should apply the CakePHP coding standards. 35 | 36 | ## Which branch to base the work 37 | 38 | * Bugfix branches will be based on develop branch. 39 | * New features that are backwards compatible will be based on develop branch 40 | * New features or other non-BC changes will go in the next major release branch. 41 | 42 | ## Submitting Changes 43 | 44 | * Push your changes to a topic branch in your fork of the repository. 45 | * Submit a pull request to the repository with the correct target branch. 46 | 47 | ## Testcases and codesniffer 48 | 49 | ErrorHandlers tests requires [PHPUnit](http://www.phpunit.de/manual/current/en/installation.html) 50 | 3.5 or higher. To run the testcases locally use the following command: 51 | 52 | ./lib/Cake/Console/cake test ErrorHandlers AllErrorHandlers 53 | 54 | To run the sniffs for CakePHP coding standards 55 | 56 | phpcs -p --extensions=php --standard=CakePHP ./app/Plugin/ErrorHandlers 57 | 58 | Check the [cakephp-codesniffer](https://github.com/cakephp/cakephp-codesniffer) 59 | repository to setup the CakePHP standard. The README contains installation info 60 | for the sniff and phpcs. 61 | 62 | 63 | # Additional Resources 64 | 65 | * [CakePHP coding standards](http://book.cakephp.org/2.0/en/contributing/cakephp-coding-conventions.html) 66 | * [Bug tracker](https://github.com/josegonzalez/cakephp-error-handlers/issues) 67 | * [General GitHub documentation](https://help.github.com/) 68 | * [GitHub pull request documentation](https://help.github.com/send-pull-requests/) 69 | * #cakephp IRC channel on freenode.org 70 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jose Diaz-Gonzalez 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 | [![Build Status](https://img.shields.io/travis/josegonzalez/php-error-handlers/master.svg?style=flat-square)](https://travis-ci.org/josegonzalez/php-error-handlers) 2 | [![Coverage Status](https://img.shields.io/coveralls/josegonzalez/php-error-handlers.svg?style=flat-square)](https://coveralls.io/r/josegonzalez/php-error-handlers?branch=master) 3 | [![Total Downloads](https://img.shields.io/packagist/dt/josegonzalez/php-error-handlers.svg?style=flat-square)](https://packagist.org/packages/josegonzalez/php-error-handlers) 4 | [![Latest Stable Version](https://img.shields.io/packagist/v/josegonzalez/php-error-handlers.svg?style=flat-square)](https://packagist.org/packages/josegonzalez/php-error-handlers) 5 | 6 | # Error Handlers 7 | 8 | A package that includes wrappers for popular error handling services. 9 | 10 | Includes an integration library for CakePHP 3. 11 | 12 | ## Requirements 13 | 14 | * PHP 5.5+ 15 | * Patience 16 | 17 | ## Installation 18 | 19 | ```shell 20 | # install it 21 | composer require josegonzalez/php-error-handlers 22 | ``` 23 | 24 | ## Usage 25 | 26 | You can register the `Handler` class as a handler of php errors and exceptions. 27 | 28 | ```php 29 | // Create an array of configuration data to pass to the handler class 30 | $config = [ 31 | 'handlers' => [ 32 | // *Can* be the class name, not-namespaced 33 | // The namespace will be "interpolated" in such cases 34 | 'NewrelicHandler' => [ 35 | ], 36 | // Can also include the full namespace 37 | 'Josegonzalez\ErrorHandlers\Handler\BugsnagHandler' => [ 38 | 'apiKey' => 'YOUR_API_KEY_HERE' 39 | ], 40 | // Invalid handlers will be ignored 41 | 'InvalidHandler' => [ 42 | ], 43 | ], 44 | ]; 45 | 46 | // Register the error handler 47 | (new \Josegonzalez\ErrorHandlers\Handler($config))->register(); 48 | 49 | // Enjoy throwing exceptions and reporting them upstream 50 | throw new \Exception('Test Exception'); 51 | ``` 52 | 53 | The registered handler returns false by default. This allows you to chain error handlers such that this package can handle reporting while another library can display user-friendly error messages. 54 | 55 | ### Available Handlers 56 | 57 | The following are built-in handlers with their configuration options: 58 | 59 | - `AirbrakeHandler`:: Uses the official [airbrake php](https://github.com/airbrake/phpbrake/) package. 60 | - `host`: (optional | default: `api.airbrake.io`) airbrake api host e.g.: 'api.airbrake.io' or 'http://errbit.example.com' 61 | - `projectId`: (required | default: `null`) 62 | - `projectKey`: (required | default: `null`) 63 | - `appVersion`: (optional | default: `null`) 64 | - `environment`: (optional | default: `null`) 65 | - `rootDirectory`: (optional | default: `null`) 66 | - `httpClient`: (optional | default: `null`) which http client to use: "default", "curl", "guzzle" or a client instance 67 | - `AtatusHandler`: Uses the `atatus` [php extension](https://www.atatus.com/docs/platforms/php). 68 | - `apiKey`: (optional | default: `null`) 69 | - `BugsnagHandler`: Uses the official [bugsnag php](https://github.com/bugsnag/bugsnag-php) package. 70 | - `apiKey`: (required | default: `null`) 71 | - `defaults`: (optional | default: `null`) Your bugsnag endpoint for enterprise users 72 | - `endpoint`: (optional | default: `true`) If we should register our default callbacks 73 | - `MonologStreamHandler`: Uses the [monolog StreamHandler](https://github.com/seldaek/monolog). 74 | - `name`: (optional | default: `error`) 75 | - `handlerClass`: (optional | default: `Monolog\Handler\StreamHandler`) 76 | - `stream`: (optional | default: `log/error.log`) 77 | - `level`: (optional | default: `Monolog\Logger::Warning`) 78 | - `NewrelicHandler`: Uses the `newrelic` [php extension](https://docs.newrelic.com/docs/agents/php-agent/getting-started/new-relic-php). 79 | - `RaygunHandler`: Uses the official [raygun php](https://github.com/MindscapeHQ/raygun4php) package. 80 | - `apiKey`: (required | default: `null`) 81 | - `SentryHandler`: Uses the official [sentry php](https://github.com/getsentry/sentry-php) package. 82 | - `dsn`: (required | default: `null`) 83 | - `callInstall`: (optional | default: `false`) Whether or not to call the `install` method on the client. 84 | 85 | ### Handler and Exception Modification 86 | 87 | #### Modifying the client handler 88 | 89 | Sometimes you may find it useful to modify the client. For instance, it may be necessary to add contextual information to the given client call. To do so, you can set the `clientCallback` configuration key: 90 | 91 | ```php 92 | $config = [ 93 | 'handlers' => [ 94 | 'BugsnagHandler' => [ 95 | 'clientCallback' => function ($client) { 96 | // do something interesting to the client 97 | $client->setAppVersion('1.0.0'); 98 | return $client; 99 | }, 100 | ], 101 | ], 102 | ]; 103 | ``` 104 | 105 | Note that the client should still respond to the existing reporting API provided by the upstream library. You may respond with a proxy library if desired, though returning the initial client is ideal. 106 | 107 | > `$client` may be set to `null` inside of `clientCallback` if the handler is improperly configured. 108 | 109 | #### Modifying the exception 110 | 111 | If necessary, it is possible to modify the exception being used within a particular handler. Changes to the exception will persist only for the duration of that particular handler call. 112 | 113 | To do so, set the `exceptionCallback` configuration key for a particular handler: 114 | 115 | ```php 116 | $config = [ 117 | 'handlers' => [ 118 | 'BugsnagHandler' => [ 119 | 'exceptionCallback' => function ($exception) { 120 | // return null to skip reporting errors 121 | if ($exception instanceof \Error) { 122 | return null; 123 | } 124 | return $exception; 125 | }, 126 | ], 127 | ], 128 | ]; 129 | ``` 130 | 131 | You may return another exception or `null`. In the latter case, the built-in handlers will skip reporting the given exception. 132 | 133 | ### Custom Handlers 134 | 135 | Each handler should implement the `Josegonzalez\ErrorHandlers\Handler\HandlerInterface`. This interface contains a single method: 136 | 137 | ```php 138 | public function handle($exception); 139 | ``` 140 | 141 | - PHP 5.x errors will be replaced with wrapper `ErrorException` instances before sent to the `handle` method. 142 | - PHP 7.x errors extending `Throwable` will be replaced with wrapper `Josegonzalez\ErrorHandlers\Exception\PHP7ErrorException` instances before sent to the `handle` method. 143 | - PHP Fatal errors will be replaced with wrapper `Josegonzalez\ErrorHandlers\Exception\FatalErrorException` instances before sent to the `handle` method. 144 | - PHP Exceptions will be sent in, unmodified. 145 | 146 | Custom handlers *should* extend the provided `Josegonzalez\ErrorHandlers\Handler\AbstractHandler` class. This gives them the ability to have configuration passed in via the provided `ConfigTrait` and custom `__construct()`. 147 | 148 | ### CakePHP Usage 149 | 150 | > Loading the library is not necessary and will result in errors. Please follow the below instructions for cakephp-specific configurations. 151 | 152 | You will want to setup at least the following configuration keys in your `config/app.php`: 153 | 154 | - `Error.config`: Takes the same configuration array as you would give for normal php usage. 155 | 156 | Next, configure the provided ErrorHandler classes in your `config/bootstrap.php`: 157 | 158 | ```php 159 | // around line 100 160 | $isCli = PHP_SAPI === 'cli'; 161 | if ($isCli) { 162 | (new \Josegonzalez\ErrorHandlers\Cake\ConsoleErrorHandler(Configure::read('Error')))->register(); 163 | } else { 164 | (new \Josegonzalez\ErrorHandlers\Cake\ErrorHandler(Configure::read('Error')))->register(); 165 | } 166 | ``` 167 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "josegonzalez/php-error-handlers", 3 | "description": "PHP Library that includes wrappers for popular error handling services", 4 | "keywords": ["cakephp", "error", "exception"], 5 | "homepage": "https://github.com/josegonzalez/php-error-handlers", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Jose Diaz-Gonzalez", 10 | "email": "php+error-handlers@josediazgonzalez.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=7.0", 15 | "bugsnag/bugsnag": "~3.0", 16 | "airbrake/phpbrake": "~0.5.0", 17 | "mindscape/raygun4php": "^1.6", 18 | "sentry/sentry": "^1.7.0", 19 | "monolog/monolog": "^1.17" 20 | }, 21 | "require-dev": { 22 | "cakephp/cakephp": "~3.0", 23 | "phpunit/phpunit": "~6.0", 24 | "cakephp/cakephp-codesniffer": "dev-master", 25 | "satooshi/php-coveralls": "~2.0" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Josegonzalez\\ErrorHandlers\\": "src", 30 | "Josegonzalez\\ErrorHandlers\\Test\\Fixture\\": "tests\\Fixture" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Josegonzalez\\ErrorHandlers\\Test\\": "tests" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'MonologStreamHandler' => [ 8 | ], 9 | // *Can* be the class name, not-namespaced 10 | 'NewrelicHandler' => [ 11 | ], 12 | // Can also include the full namespace 13 | 'Josegonzalez\ErrorHandlers\Handler\BugsnagHandler' => [ 14 | 'apiKey' => 'YOUR_API_KEY_HERE' 15 | ], 16 | ], 17 | ]; 18 | 19 | // Register the error handler 20 | (new \Josegonzalez\ErrorHandlers\Handler($config))->register(); 21 | 22 | // Enjoy throwing exceptions and reporting them upstream 23 | throw new \Exception('Test Exception'); 24 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ./tests/TestCase 17 | 18 | 19 | 20 | 21 | 22 | ./docs 23 | ./vendor 24 | ./tests/bootstrap.php 25 | 26 | 29 | 30 | src 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/Cake/ConsoleErrorHandler.php: -------------------------------------------------------------------------------- 1 | handle($exception); 25 | 26 | return parent::handleError($code, $description, $file, $line, $context); 27 | } 28 | 29 | /** 30 | * Handle uncaught exceptions. 31 | * 32 | * @param \Exception $exception Exception instance. 33 | * @return void 34 | * @throws \Exception When renderer class not found 35 | * @see http://php.net/manual/en/function.set-exception-handler.php 36 | */ 37 | public function handleException(Exception $exception) 38 | { 39 | $this->handle($exception); 40 | 41 | return parent::handleException($exception); 42 | } 43 | 44 | /** 45 | * Handle uncaught exceptions. 46 | * 47 | * Iterates over the configured error handlers and invokes them in order 48 | * 49 | * @param Throwable|Exception $exception A Throwable or Exception instance 50 | * @return void 51 | * @see http://php.net/manual/en/function.set-exception-handler.php 52 | */ 53 | public function handle($exception) 54 | { 55 | $handlers = (array)Configure::read('Error.config.handlers'); 56 | foreach ($handlers as $handler => $config) { 57 | $handlerClass = $handler; 58 | if (!class_exists($handlerClass)) { 59 | $handlerClass = 'Josegonzalez\ErrorHandlers\Handler\\' . $handler; 60 | } 61 | if (!class_exists($handlerClass)) { 62 | $handlerClass = 'Josegonzalez\ErrorHandlers\Handler\\' . $handler . 'Handler'; 63 | } 64 | if (!class_exists($handlerClass)) { 65 | continue; 66 | } 67 | $instance = new $handlerClass((array)$config); 68 | $instance->handle($exception); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Cake/ErrorHandler.php: -------------------------------------------------------------------------------- 1 | handle($exception); 25 | 26 | return parent::handleError($code, $description, $file, $line, $context); 27 | } 28 | 29 | /** 30 | * Handle uncaught exceptions. 31 | * 32 | * @param \Exception $exception Exception instance. 33 | * @return void 34 | * @throws \Exception When renderer class not found 35 | * @see http://php.net/manual/en/function.set-exception-handler.php 36 | */ 37 | public function handleException(Exception $exception) 38 | { 39 | $this->handle($exception); 40 | 41 | return parent::handleException($exception); 42 | } 43 | 44 | /** 45 | * Handle uncaught exceptions. 46 | * 47 | * Iterates over the configured error handlers and invokes them in order 48 | * 49 | * @param Throwable|Exception $exception A Throwable or Exception instance 50 | * @return void 51 | * @see http://php.net/manual/en/function.set-exception-handler.php 52 | */ 53 | public function handle($exception) 54 | { 55 | $handlers = (array)Configure::read('Error.config.handlers'); 56 | foreach ($handlers as $handler => $config) { 57 | $handlerClass = $handler; 58 | if (!class_exists($handlerClass)) { 59 | $handlerClass = 'Josegonzalez\ErrorHandlers\Handler\\' . $handler; 60 | } 61 | if (!class_exists($handlerClass)) { 62 | $handlerClass = 'Josegonzalez\ErrorHandlers\Handler\\' . $handler . 'Handler'; 63 | } 64 | if (!class_exists($handlerClass)) { 65 | continue; 66 | } 67 | $instance = new $handlerClass((array)$config); 68 | $instance->handle($exception); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Exception/FatalErrorException.php: -------------------------------------------------------------------------------- 1 | file = $file; 26 | } 27 | if ($line) { 28 | $this->line = $line; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Exception/PHP7ErrorException.php: -------------------------------------------------------------------------------- 1 | error = $error; 30 | $message = $error->getMessage(); 31 | $code = $error->getCode(); 32 | parent::__construct(sprintf('(%s) - %s', get_class($error), $message), $code); 33 | } 34 | 35 | /** 36 | * Returns the wrapped error object 37 | * 38 | * @return Error 39 | */ 40 | public function getError() 41 | { 42 | return $this->error; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Handler.php: -------------------------------------------------------------------------------- 1 | config($config); 23 | $this->configDefault('errorLevel', -1); 24 | $this->configDefault('handlers', []); 25 | } 26 | 27 | /** 28 | * Register the error and exception handlers. 29 | * 30 | * @return void 31 | */ 32 | public function register() 33 | { 34 | $errorLevel = $this->config('errorLevel'); 35 | set_error_handler([$this, 'handleError'], $errorLevel); 36 | set_exception_handler([$this, 'handle']); 37 | register_shutdown_function(function () { 38 | if (PHP_SAPI === 'cli') { 39 | return; 40 | } 41 | $error = error_get_last(); 42 | if (!is_array($error)) { 43 | return; 44 | } 45 | $fatals = [ 46 | E_USER_ERROR, 47 | E_ERROR, 48 | E_PARSE, 49 | ]; 50 | if (!in_array($error['type'], $fatals, true)) { 51 | return; 52 | } 53 | $this->handleFatalError( 54 | $error['type'], 55 | $error['message'], 56 | $error['file'], 57 | $error['line'] 58 | ); 59 | }); 60 | } 61 | 62 | /** 63 | * Handle a fatal error. 64 | * 65 | * @param int $code Code of error 66 | * @param string $description Error description 67 | * @param string $file File on which error occurred 68 | * @param int $line Line that triggered the error 69 | * @return bool 70 | */ 71 | public function handleFatalError($code, $description, $file = null, $line = null) 72 | { 73 | $exception = new FatalErrorException($description, $code, $file, $line); 74 | 75 | return $this->handle($exception); 76 | } 77 | 78 | /** 79 | * Set as the default error handler 80 | * 81 | * @param int $code Code of error 82 | * @param string $description Error description 83 | * @param string|null $file File on which error occurred 84 | * @param int|null $line Line that triggered the error 85 | * @param array|null $context Context 86 | * @return bool True if error was handled 87 | */ 88 | public function handleError($code, $description, $file = null, $line = null, $context = null) 89 | { 90 | $context; 91 | $exception = new ErrorException($description, 0, $code, $file, $line); 92 | 93 | return $this->handle($exception); 94 | } 95 | 96 | /** 97 | * Handle uncaught exceptions. 98 | * 99 | * Uses a template method provided by subclasses to display errors in an 100 | * environment appropriate way. 101 | * 102 | * @param \Exception $exception Exception instance. 103 | * @return void 104 | * @throws \Exception When renderer class not found 105 | * @see http://php.net/manual/en/function.set-exception-handler.php 106 | */ 107 | public function handleException($exception) 108 | { 109 | if ($exception instanceof Error) { 110 | $exception = new PHP7ErrorException($exception); 111 | } 112 | 113 | return $this->handle($exception); 114 | } 115 | 116 | /** 117 | * Handle uncaught exceptions. 118 | * 119 | * Iterates over the configured error handlers and invokes them in order 120 | * 121 | * @param Throwable|Exception $exception A Throwable or Exception instance 122 | * @return void 123 | * @see http://php.net/manual/en/function.set-exception-handler.php 124 | */ 125 | public function handle($exception) 126 | { 127 | $handlers = (array)$this->config('handlers'); 128 | foreach ($handlers as $handler => $config) { 129 | $handlerClass = $handler; 130 | if (!class_exists($handlerClass)) { 131 | $handlerClass = 'Josegonzalez\ErrorHandlers\Handler\\' . $handler; 132 | } 133 | if (!class_exists($handlerClass)) { 134 | $handlerClass = 'Josegonzalez\ErrorHandlers\Handler\\' . $handler . 'Handler'; 135 | } 136 | if (!class_exists($handlerClass)) { 137 | continue; 138 | } 139 | $instance = new $handlerClass((array)$config); 140 | $instance->handle($exception); 141 | } 142 | 143 | return false; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Handler/AbstractHandler.php: -------------------------------------------------------------------------------- 1 | config($config); 26 | $this->configDefault('clientCallback', function ($client) { 27 | return $client; 28 | }); 29 | $this->configDefault('exceptionCallback', function ($exception) { 30 | return $exception; 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Handler/AirbrakeHandler.php: -------------------------------------------------------------------------------- 1 | config('exceptionCallback'), $exception); 18 | if (!$exception) { 19 | return; 20 | } 21 | $client = $this->client(); 22 | $client = call_user_func($this->config('clientCallback'), $client); 23 | if ($client) { 24 | $client->notify($exception); 25 | } 26 | } 27 | 28 | /** 29 | * Returns a client 30 | * 31 | * @return \Airbrake\Notifier 32 | */ 33 | protected function client() 34 | { 35 | $projectId = $this->config('projectId'); 36 | $projectKey = $this->config('projectKey'); 37 | if (!$projectId || !$projectKey) { 38 | return null; 39 | } 40 | 41 | $config = $this->config(); 42 | $client = new Notifier($config); 43 | 44 | return $client; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Handler/AtatusHandler.php: -------------------------------------------------------------------------------- 1 | config('exceptionCallback'), $exception); 17 | if (!$exception) { 18 | return; 19 | } 20 | if (!extension_loaded('atatus')) { 21 | return; 22 | } 23 | $apiKey = $this->config('apiKey'); 24 | if (!empty($apiKey)) { 25 | atatus_set_api_key($apiKey); 26 | } 27 | atatus_notify_exception($exception); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Handler/BugsnagHandler.php: -------------------------------------------------------------------------------- 1 | config('exceptionCallback'), $exception); 18 | if (!$exception) { 19 | return; 20 | } 21 | $client = $this->client(); 22 | $client = call_user_func($this->config('clientCallback'), $client); 23 | if ($client) { 24 | $client->notifyException($exception); 25 | } 26 | } 27 | 28 | /** 29 | * Returns a client 30 | * 31 | * @return \Bugsnag_Client 32 | */ 33 | protected function client() 34 | { 35 | $apiKey = $this->config('apiKey'); 36 | if (!$apiKey) { 37 | return null; 38 | } 39 | 40 | $endpoint = $this->config('endpoint'); 41 | $defaults = $this->config('defaults'); 42 | if ($defaults === null) { 43 | $defaults = true; 44 | } 45 | $client = Client::make($apiKey, $endpoint, $defaults); 46 | 47 | return $client; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Handler/HandlerInterface.php: -------------------------------------------------------------------------------- 1 | configDefault('name', 'error'); 19 | $this->configDefault('handlerClass', 'Monolog\Handler\StreamHandler'); 20 | $this->configDefault('stream', 'log/error.log'); 21 | $this->configDefault('level', Logger::WARNING); 22 | } 23 | 24 | /** 25 | * Handles a given exception 26 | * 27 | * @param Throwable|Exception $exception A Throwable or Exception instance 28 | * @return void 29 | */ 30 | public function handle($exception) 31 | { 32 | $exception = call_user_func($this->config('exceptionCallback'), $exception); 33 | if (!$exception) { 34 | return; 35 | } 36 | $client = $this->client(); 37 | $client = call_user_func($this->config('clientCallback'), $client); 38 | if ($client) { 39 | $client->addError(sprintf('%s: %s', get_class($exception), $exception->getMessage())); 40 | } 41 | } 42 | 43 | /** 44 | * Returns a client 45 | * 46 | * @return \Monolog\Logger 47 | */ 48 | protected function client() 49 | { 50 | $handlerClass = $this->config('handlerClass'); 51 | $level = $this->config('level'); 52 | $stream = $this->config('stream'); 53 | $log = new Logger($this->config('name')); 54 | $log->pushHandler(new $handlerClass($stream, $level)); 55 | 56 | return $log; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Handler/NewrelicHandler.php: -------------------------------------------------------------------------------- 1 | config('exceptionCallback'), $exception); 17 | if (!$exception) { 18 | return; 19 | } 20 | if (extension_loaded('newrelic')) { 21 | newrelic_notice_error($exception); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Handler/RaygunHandler.php: -------------------------------------------------------------------------------- 1 | config('exceptionCallback'), $exception); 18 | if (!$exception) { 19 | return; 20 | } 21 | $client = $this->client(); 22 | $client = call_user_func($this->config('clientCallback'), $client); 23 | if ($client) { 24 | $client->SendException($exception); 25 | } 26 | } 27 | 28 | /** 29 | * Returns a client 30 | * 31 | * @return \Raygun4php\RaygunClient 32 | */ 33 | protected function client() 34 | { 35 | $apiKey = $this->config('apiKey'); 36 | if (!$apiKey) { 37 | return null; 38 | } 39 | 40 | $client = new RaygunClient($apiKey); 41 | 42 | return $client; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Handler/SentryHandler.php: -------------------------------------------------------------------------------- 1 | config('exceptionCallback'), $exception); 18 | if (!$exception) { 19 | return; 20 | } 21 | $client = $this->client(); 22 | $client = call_user_func($this->config('clientCallback'), $client); 23 | if ($client) { 24 | $client->captureException($exception); 25 | } 26 | } 27 | 28 | /** 29 | * Returns a client 30 | * 31 | * @return \Raven_Client 32 | */ 33 | protected function client() 34 | { 35 | $dsn = $this->config('dsn'); 36 | if (!$dsn) { 37 | return null; 38 | } 39 | 40 | $client = new Raven_Client($dsn); 41 | $callInstall = $this->config('callInstall'); 42 | if ($callInstall === true) { 43 | $client->install(); 44 | } 45 | 46 | return $client; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Utility/ConfigTrait.php: -------------------------------------------------------------------------------- 1 | config($key); 16 | if ($value === null) { 17 | $this->config($key, $default); 18 | } 19 | } 20 | 21 | /** 22 | * ### Usage 23 | * 24 | * Reading the whole config: 25 | * 26 | * ``` 27 | * $this->config(); 28 | * ``` 29 | * 30 | * Reading a specific value: 31 | * 32 | * ``` 33 | * $this->config('key'); 34 | * ``` 35 | * 36 | * Reading a nested value: 37 | * 38 | * ``` 39 | * $this->config('some.nested.key'); 40 | * ``` 41 | * 42 | * Setting a specific value: 43 | * 44 | * ``` 45 | * $this->config('key', $value); 46 | * ``` 47 | * 48 | * Setting a nested value: 49 | * 50 | * ``` 51 | * $this->config('some.nested.key', $value); 52 | * ``` 53 | * 54 | * Updating all config settings at the same time: 55 | * 56 | * ``` 57 | * $this->config(['one' => 'value', 'another' => 'value']); 58 | * ``` 59 | * 60 | * @param string|array|null $key The key to get/set, or a complete array of configs. 61 | * @param mixed|null $value The value to set. 62 | * @return mixed Config value being read, or the data itself on write operations. 63 | */ 64 | public function config($key = null, $value = null) 65 | { 66 | if ($key === null) { 67 | return $this->config; 68 | } 69 | if (is_array($key)) { 70 | return $this->config = $key; 71 | } 72 | if ($value === null) { 73 | if (isset($this->config[$key])) { 74 | return $this->config[$key]; 75 | } 76 | } 77 | 78 | return $this->config[$key] = $value; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 'App']); 35 | Cake\Core\Configure::write('debug', true); 36 | 37 | $TMP = new \Cake\Filesystem\Folder(TMP); 38 | $TMP->create(TMP . 'cache/models', 0777); 39 | $TMP->create(TMP . 'cache/persistent', 0777); 40 | $TMP->create(TMP . 'cache/views', 0777); 41 | 42 | $cache = [ 43 | 'default' => [ 44 | 'engine' => 'File' 45 | ], 46 | '_cake_core_' => [ 47 | 'className' => 'File', 48 | 'prefix' => 'error_handlers_myapp_cake_core_', 49 | 'path' => CACHE . 'persistent/', 50 | 'serialize' => true, 51 | 'duration' => '+10 seconds' 52 | ], 53 | '_cake_model_' => [ 54 | 'className' => 'File', 55 | 'prefix' => 'error_handlers_my_app_cake_model_', 56 | 'path' => CACHE . 'models/', 57 | 'serialize' => 'File', 58 | 'duration' => '+10 seconds' 59 | ] 60 | ]; 61 | 62 | Cake\Cache\Cache::config($cache); 63 | Cake\Core\Configure::write('Session', [ 64 | 'defaults' => 'php' 65 | ]); 66 | 67 | Cake\Core\Plugin::load('Josegonzalez/ErrorHandlers', ['path' => ROOT . DS, 'autoload' => true]); 68 | --------------------------------------------------------------------------------