From d3d18d2682c1f530a1759d1618f21a3330528e10 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 14 Dec 2019 12:09:31 +0700 Subject: [PATCH] added ability to exclude specific PHP E_* error and Exception with specific message --- README.md | 21 +++- config/error-hero-module.local.php.dist | 21 +++- ...xpressive-error-hero-module.local.php.dist | 21 +++- .../error-hero-module.local.php | 105 +++++++++++++++++ ...rollerForSpecificErrorAndExceptionSpec.php | 107 ++++++++++++++++++ src/Handler/Formatter/Json.php | 2 +- src/HeroFunction.php | 18 +++ src/HeroTrait.php | 11 +- src/Listener/Mvc.php | 9 +- src/Middleware/Expressive.php | 10 +- 10 files changed, 302 insertions(+), 23 deletions(-) create mode 100644 spec/Fixture/config/autoload-for-specific-error-and-exception/error-hero-module.local.php create mode 100644 spec/Integration/IntegrationViaErrorPreviewControllerForSpecificErrorAndExceptionSpec.php diff --git a/README.md b/README.md index ffe2b194..fa656be8 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ Features - [x] Save to DB with Db Writer Adapter. - [x] Log Exception (dispatch.error and render.error) and PHP Errors in all events process. -- [x] Support excludes [PHP E_* Error](http://www.php.net/manual/en/errorfunc.constants.php) (eg: exclude E_USER_DEPRECATED) in config settings. -- [x] Support excludes [PHP Exception](http://php.net/manual/en/spl.exceptions.php) (eg: Exception class or classes that extends it) in config settings. +- [x] Support excludes [PHP E_* Error](http://www.php.net/manual/en/errorfunc.constants.php) (eg: exclude E_USER_DEPRECATED or specific E_USER_DEPRECATED with specific message) in config settings. +- [x] Support excludes [PHP Exception](http://php.net/manual/en/spl.exceptions.php) (eg: Exception class or classes that extends it or specific exception class with specific message) in config settings. - [x] Handle only once log error for same error per configured time range. - [x] Set default page (web access) or default message (console access) for error if configured 'display_errors' = 0. - [x] Set default content when request is XMLHttpRequest via 'ajax' configuration. @@ -200,12 +200,27 @@ return [ // excluded php errors ( http://www.php.net/manual/en/errorfunc.constants.php ) 'exclude-php-errors' => [ + + // can be specific error \E_USER_DEPRECATED, + + // can be specific error with specific message + [\E_WARNING, 'specific error message'], + ], // excluded exceptions 'exclude-exceptions' => [ - \App\Exception\MyException::class, // can be an Exception class or class extends Exception class + + // can be an Exception class or class extends Exception class + \App\Exception\MyException::class, + + // can be specific exception with specific message + [\RuntimeException::class, 'specific exception message'], + + // or specific Error class with specific message + [\Error::class, 'specific error message'], + ], // show or not error diff --git a/config/error-hero-module.local.php.dist b/config/error-hero-module.local.php.dist index 0a7c72ab..94a908a4 100644 --- a/config/error-hero-module.local.php.dist +++ b/config/error-hero-module.local.php.dist @@ -53,14 +53,29 @@ return [ 'display-settings' => [ - // excluded php errors + // excluded php errors ( http://www.php.net/manual/en/errorfunc.constants.php ) 'exclude-php-errors' => [ - \E_USER_DEPRECATED + + // can be specific error + \E_USER_DEPRECATED, + + // can be specific error with specific message + [\E_WARNING, 'specific error message'], + ], // excluded exceptions 'exclude-exceptions' => [ - \Application\Exception\MyException::class, // can be an Exception class or class extends Exception class + + // can be an Exception class or class extends Exception class + \App\Exception\MyException::class, + + // can be specific exception with specific message + [\RuntimeException::class, 'specific exception message'], + + // or specific Error class with specific message + [\Error::class, 'specific error message'], + ], // show or not error diff --git a/config/expressive-error-hero-module.local.php.dist b/config/expressive-error-hero-module.local.php.dist index 8ab48e00..2dff29db 100644 --- a/config/expressive-error-hero-module.local.php.dist +++ b/config/expressive-error-hero-module.local.php.dist @@ -49,14 +49,29 @@ return [ 'display-settings' => [ - // excluded php errors + // excluded php errors ( http://www.php.net/manual/en/errorfunc.constants.php ) 'exclude-php-errors' => [ - \E_USER_DEPRECATED + + // can be specific error + \E_USER_DEPRECATED, + + // can be specific error with specific message + [\E_WARNING, 'specific error message'], + ], // excluded exceptions 'exclude-exceptions' => [ - \App\Exception\MyException::class, // can be an Exception class or class extends Exception class + + // can be an Exception class or class extends Exception class + \App\Exception\MyException::class, + + // can be specific exception with specific message + [\RuntimeException::class, 'specific exception message'], + + // or specific Error class with specific message + [\Error::class, 'specific error message'], + ], // show or not error diff --git a/spec/Fixture/config/autoload-for-specific-error-and-exception/error-hero-module.local.php b/spec/Fixture/config/autoload-for-specific-error-and-exception/error-hero-module.local.php new file mode 100644 index 00000000..060fe23e --- /dev/null +++ b/spec/Fixture/config/autoload-for-specific-error-and-exception/error-hero-module.local.php @@ -0,0 +1,105 @@ + [ + 'username' => 'root', + 'password' => '', + 'driver' => 'Pdo', + 'dsn' => 'mysql:dbname=errorheromodule;host=127.0.0.1', + 'driver_options' => [ + \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'', + ], + ], + + 'log' => [ + 'ErrorHeroModuleLogger' => [ + 'writers' => [ + + [ + 'name' => 'db', + 'options' => [ + 'db' => AdapterInterface::class, + 'table' => 'log', + 'column' => [ + 'timestamp' => 'date', + 'priority' => 'type', + 'message' => 'event', + 'extra' => [ + 'url' => 'url', + 'file' => 'file', + 'line' => 'line', + 'error_type' => 'error_type', + 'trace' => 'trace', + 'request_data' => 'request_data', + ], + ], + ], + ], + + ], + ], + ], + + 'error-hero-module' => [ + 'enable' => true, + 'display-settings' => [ + + // excluded php errors ( http://www.php.net/manual/en/errorfunc.constants.php ) + 'exclude-php-errors' => [ + + // can be specific error with specific message + [\E_NOTICE, 'Undefined offset: 1'], + + ], + + // excluded exceptions + 'exclude-exceptions' => [ + + // can be specific exception instance with specific error message + [\Exception::class, 'a sample exception preview'], + + // or Error class + [\Error::class, 'a sample error preview'], + ], + + // show or not error + 'display_errors' => 0, + + // if enable and display_errors = 0, the page will bring layout and view + 'template' => [ + 'layout' => 'layout/layout', + 'view' => 'error-hero-module/error-default' + ], + + // if enable and display_errors = 0, the console will bring message + 'console' => [ + 'message' => 'We have encountered a problem and we can not fulfill your request. An error report has been generated and sent to the support team and someone will attend to this problem urgently. Please try again later. Thank you for your patience.', + ], + + ], + 'logging-settings' => [ + 'same-error-log-time-range' => 86400, + ], + 'email-notification-settings' => [ + // set to true to activate email notification on log error + 'enable' => false, + + // Zend\Mail\Message instance registered at service manager + 'mail-message' => 'YourMailMessageService', + + // Zend\Mail\Transport\TransportInterface instance registered at service manager + 'mail-transport' => 'YourMailTransportService', + + // email sender + 'email-from' => 'Sender Name ', + + 'email-to-send' => [ + 'developer1@foo.com', + 'developer2@foo.com', + ], + ], + ], +]; diff --git a/spec/Integration/IntegrationViaErrorPreviewControllerForSpecificErrorAndExceptionSpec.php b/spec/Integration/IntegrationViaErrorPreviewControllerForSpecificErrorAndExceptionSpec.php new file mode 100644 index 00000000..2019933a --- /dev/null +++ b/spec/Integration/IntegrationViaErrorPreviewControllerForSpecificErrorAndExceptionSpec.php @@ -0,0 +1,107 @@ + [ + 'Zend\Router', + 'Zend\Db', + 'ErrorHeroModule', + ], + 'module_listener_options' => [ + 'config_glob_paths' => [ + \realpath(__DIR__).'/../Fixture/config/autoload-for-specific-error-and-exception/error-hero-module.local.php', + \realpath(__DIR__).'/../Fixture/config/module.local.php', + ], + ], + ]); + + $events = $application->getEventManager(); + $serviceManager = $application->getServiceManager(); + $serviceManager->get('SendResponseListener') + ->detach($events); + + return $application; + + }); + + describe('/error-preview', function() { + + it('empty as rely to original mvc process to handle', function() { + + $request = $this->application->getRequest(); + $request->setMethod('GET'); + $request->setUri('http://example.com/error-preview'); + $request->setRequestUri('/error-preview'); + + \ob_start(); + $this->application->run(); + $content = \ob_get_clean(); + + expect(\ob_get_clean())->toBe(''); + expect($this->application->getResponse()->getStatusCode())->toBe(500); + + // $select = $this->tableGateway->getSql()->select(); + // echo $select->count(); + + }); + + }); + + describe('/error-preview/notice', function() { + + it('empty as rely to original mvc process to handle', function() { + + $request = $this->application->getRequest(); + $request->setMethod('GET'); + $request->setUri('http://example.com/error-preview/notice'); + $request->setRequestUri('/error-preview/notice'); + + \ob_start(); + $this->application->run(); + $content = \ob_get_clean(); + + expect(\ob_get_clean())->toBe(''); + $this->application->getResponse()->setStatusCode(http_response_code()); + expect($this->application->getResponse()->getStatusCode())->toBe(500); + + + }); + + }); + + describe('/error-preview/error', function() { + + it('empty as rely to original mvc process to handle', function() { + + $request = $this->application->getRequest(); + $request->setMethod('GET'); + $request->setUri('http://example.com/error-preview/error'); + $request->setRequestUri('/error-preview/error'); + + \ob_start(); + $this->application->run(); + $content = \ob_get_clean(); + + expect(\ob_get_clean())->toBe(''); + expect($this->application->getResponse()->getStatusCode())->toBe(500); + + + }); + + }); + +}); diff --git a/src/Handler/Formatter/Json.php b/src/Handler/Formatter/Json.php index 36b32d78..f258d7ed 100644 --- a/src/Handler/Formatter/Json.php +++ b/src/Handler/Formatter/Json.php @@ -5,8 +5,8 @@ namespace ErrorHeroModule\Handler\Formatter; use DateTime; -use Zend\Log\Formatter\Json as BaseJson; use Zend\Log\Formatter\FormatterInterface; +use Zend\Log\Formatter\Json as BaseJson; class Json extends BaseJson implements FormatterInterface { diff --git a/src/HeroFunction.php b/src/HeroFunction.php index 7f8b1684..a5434971 100644 --- a/src/HeroFunction.php +++ b/src/HeroFunction.php @@ -5,6 +5,7 @@ namespace ErrorHeroModule; use Seld\JsonLint\JsonParser; +use Throwable; function detectMessageContentType(string $message) : string { @@ -12,3 +13,20 @@ function detectMessageContentType(string $message) : string ? 'application/problem+json' : ((\strip_tags($message) === $message) ? 'text/plain' : 'text/html'); } + +function isExcludedException(array $excludeExceptionsConfig, Throwable $t) +{ + $exceptionOrErrorClass = \get_class($t); + + foreach ($excludeExceptionsConfig as $excludeException) { + if ($exceptionOrErrorClass === $excludeException) { + return true; + } + + if (is_array($excludeException) && $excludeException[0] === $exceptionOrErrorClass && $excludeException[1] === $t->getMessage()) { + return true; + } + } + + return false; +} diff --git a/src/HeroTrait.php b/src/HeroTrait.php index 71704aa3..e2a6b749 100644 --- a/src/HeroTrait.php +++ b/src/HeroTrait.php @@ -104,8 +104,15 @@ public function phpErrorHandler(int $errorType, string $errorMessage, string $er return; } - if (\in_array($errorType, $this->errorHeroModuleConfig['display-settings']['exclude-php-errors'])) { - return; + \http_response_code(500); + foreach ($this->errorHeroModuleConfig['display-settings']['exclude-php-errors'] as $excludePhpError) { + if ($errorType === $excludePhpError) { + return; + } + + if (is_array($excludePhpError) && $excludePhpError[0] === $errorType && $excludePhpError[1] === $errorMessage) { + return; + } } throw new ErrorException($errorMessage, 0, $errorType, $errorFile, $errorLine); diff --git a/src/Listener/Mvc.php b/src/Listener/Mvc.php index ff2add74..44d9c15c 100644 --- a/src/Listener/Mvc.php +++ b/src/Listener/Mvc.php @@ -4,8 +4,10 @@ namespace ErrorHeroModule\Listener; +use function ErrorHeroModule\detectMessageContentType; use ErrorHeroModule\Handler\Logging; use ErrorHeroModule\HeroTrait; +use function ErrorHeroModule\isExcludedException; use Webmozart\Assert\Assert; use Zend\Console\Response as ConsoleResponse; use Zend\EventManager\AbstractListenerAggregate; @@ -14,11 +16,10 @@ use Zend\Http\PhpEnvironment\Response; use Zend\Mvc\MvcEvent; use Zend\Stdlib\RequestInterface; + use Zend\Text\Table; use Zend\View\Renderer\PhpRenderer; -use function ErrorHeroModule\detectMessageContentType; - class Mvc extends AbstractListenerAggregate { use HeroTrait; @@ -64,9 +65,7 @@ public function exceptionError(MvcEvent $e) : void return; } - $exceptionClass = \get_class($exception); - if (isset($this->errorHeroModuleConfig['display-settings']['exclude-exceptions']) && - \in_array($exceptionClass, $this->errorHeroModuleConfig['display-settings']['exclude-exceptions'])) { + if (isset($this->errorHeroModuleConfig['display-settings']['exclude-exceptions']) && isExcludedException($this->errorHeroModuleConfig['display-settings']['exclude-exceptions'], $exception)) { // rely on original mvc process return; } diff --git a/src/Middleware/Expressive.php b/src/Middleware/Expressive.php index 60f2744c..30833a7b 100644 --- a/src/Middleware/Expressive.php +++ b/src/Middleware/Expressive.php @@ -6,8 +6,10 @@ use Closure; use Error; +use function ErrorHeroModule\detectMessageContentType; use ErrorHeroModule\Handler\Logging; use ErrorHeroModule\HeroTrait; +use function ErrorHeroModule\isExcludedException; use Exception; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -18,11 +20,10 @@ use Zend\Diactoros\Response\HtmlResponse; use Zend\Expressive\Template\TemplateRendererInterface; use Zend\Expressive\ZendView\ZendViewRenderer; + use Zend\Psr7Bridge\Psr7ServerRequest; use Zend\View\Model\ViewModel; -use function ErrorHeroModule\detectMessageContentType; - class Expressive implements MiddlewareInterface { use HeroTrait; @@ -69,10 +70,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface */ public function exceptionError(Throwable $t) : ResponseInterface { - $exceptionOrErrorClass = \get_class($t); - if (isset($this->errorHeroModuleConfig['display-settings']['exclude-exceptions']) && - \in_array($exceptionOrErrorClass, $this->errorHeroModuleConfig['display-settings']['exclude-exceptions']) - ) { + if (isset($this->errorHeroModuleConfig['display-settings']['exclude-exceptions']) && isExcludedException($this->errorHeroModuleConfig['display-settings']['exclude-exceptions'], $t)) { throw $t; }