diff --git a/.gitignore b/.gitignore index fc43a6e..ea60caf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.idea/ vendor/ .php_cs.cache +.phpunit.result.cache composer.lock diff --git a/.travis.yml b/.travis.yml index ed5ee33..67c275d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,9 @@ sudo: false matrix: fast_finish: true include: - - php: 7.1 - env: CS_CHECK=1 STATIC_ANALYSIS=1 - - php: 7.2 - php: 7.3 + env: CS_CHECK=1 STATIC_ANALYSIS=1 + - php: 7.4 env: CODE_COVERAGE=1 cache: diff --git a/composer.json b/composer.json index cf173fb..dd92f3a 100644 --- a/composer.json +++ b/composer.json @@ -11,18 +11,18 @@ } ], "require": { - "php": "^7.1" + "php": "^7.3" }, "require-dev": { - "phpstan/phpstan": "^0.11", - "phpstan/phpstan-phpunit": "^0.11", - "phpunit/phpunit": "^7.5", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^8.5", "roave/security-advisories": "dev-master", - "slam/php-cs-fixer-extensions": "^1.18", + "slam/php-cs-fixer-extensions": "^1.19", "slam/php-debug-r": "^1.6", - "slam/phpstan-extensions": "^3.6", - "symfony/console": "^4.3", - "thecodingmachine/phpstan-strict-rules": "^0.11" + "slam/phpstan-extensions": "^4.0", + "symfony/console": "^5.0", + "thecodingmachine/phpstan-strict-rules": "^0.12" }, "autoload": { "psr-4": { diff --git a/lib/ErrorHandler.php b/lib/ErrorHandler.php index 9f1a745..754259f 100644 --- a/lib/ErrorHandler.php +++ b/lib/ErrorHandler.php @@ -5,24 +5,67 @@ namespace Slam\ErrorHandler; use Doctrine\Common\Util\Debug as DoctrineDebug; +use ErrorException; +use Throwable; final class ErrorHandler { + /** + * @var bool + */ private $autoExit = true; + + /** + * @var null|bool + */ private $cli; + + /** + * @var null|int + */ private $terminalWidth; + + /** + * @var null|resource + */ private $errorOutputStream; + + /** + * @var bool + */ private $hasColorSupport = false; + + /** + * @var null|bool + */ private $logErrors; + + /** + * @var bool + */ private $logVariables = true; + + /** + * @var callable + */ private $emailCallback; + + /** + * @var array + */ private $scream = []; + /** + * @var array + */ private static $colors = [ '' => "\033[37;41m", '' => "\033[0m", ]; + /** + * @var array + */ private static $errors = [ \E_COMPILE_ERROR => 'E_COMPILE_ERROR', \E_COMPILE_WARNING => 'E_COMPILE_WARNING', @@ -65,6 +108,7 @@ public function isCli(): bool { if (null === $this->cli) { $this->setCli(\PHP_SAPI === 'cli'); + \assert(null !== $this->cli); } return $this->cli; @@ -85,11 +129,15 @@ public function getTerminalWidth(): int } $this->setTerminalWidth((int) $width ?: 80); + \assert(null !== $this->terminalWidth); } return $this->terminalWidth; } + /** + * @param mixed $errorOutputStream + */ public function setErrorOutputStream($errorOutputStream): void { if (! \is_resource($errorOutputStream)) { @@ -100,10 +148,14 @@ public function setErrorOutputStream($errorOutputStream): void $this->hasColorSupport = (\function_exists('posix_isatty') && @\posix_isatty($errorOutputStream)); } + /** + * @return resource + */ public function getErrorOutputStream() { if (null === $this->errorOutputStream) { $this->setErrorOutputStream(\STDERR); + \assert(null !== $this->errorOutputStream); } return $this->errorOutputStream; @@ -118,6 +170,7 @@ public function logErrors(): bool { if (null === $this->logErrors) { $this->setLogErrors(! \interface_exists(\PHPUnit\Framework\Test::class)); + \assert(null !== $this->logErrors); } return $this->logErrors; @@ -133,11 +186,17 @@ public function logVariables(): bool return $this->logVariables; } + /** + * @param array $scream + */ public function setScreamSilencedErrors(array $scream): void { $this->scream = $scream; } + /** + * @return array + */ public function getScreamSilencedErrors(): array { return $this->scream; @@ -149,6 +208,12 @@ public function register(): void \set_exception_handler([$this, 'exceptionHandler']); } + /** + * @param int $errno + * @param string $errstr + * @param string $errfile + * @param int $errline + */ public function errorHandler($errno, $errstr = '', $errfile = '', $errline = 0): void { // Mandatory check for @ operator @@ -156,10 +221,10 @@ public function errorHandler($errno, $errstr = '', $errfile = '', $errline = 0): return; } - throw new \ErrorException($errstr, $errno, $errno, $errfile, $errline); + throw new ErrorException($errstr, $errno, $errno, $errfile, $errline); } - public function exceptionHandler(\Throwable $exception): void + public function exceptionHandler(Throwable $exception): void { $this->logException($exception); $this->emailException($exception); @@ -256,7 +321,7 @@ private function outputError(string $text): void \fwrite($this->getErrorOutputStream(), \str_replace(\array_keys(self::$colors), $this->hasColorSupport ? \array_values(self::$colors) : '', $text) . \PHP_EOL); } - public function logException(\Throwable $exception): void + public function logException(Throwable $exception): void { if (! $this->logErrors()) { return; @@ -280,7 +345,7 @@ public function logException(\Throwable $exception): void } while ($exception = $exception->getPrevious()); } - public function emailException(\Throwable $exception): void + public function emailException(Throwable $exception): void { if (! $this->logErrors()) { return; @@ -348,16 +413,16 @@ public function emailException(\Throwable $exception): void try { $callback($subject, $bodyText); - } catch (\Throwable $e) { + } catch (Throwable $e) { $this->logException($e); } } - private function getExceptionCode(\Throwable $exception): string + private function getExceptionCode(Throwable $exception): string { $code = $exception->getCode(); - if ($exception instanceof \ErrorException && isset(static::$errors[$code])) { - $code = static::$errors[$code]; + if ($exception instanceof ErrorException && isset(self::$errors[$code])) { + $code = self::$errors[$code]; } return (string) $code; diff --git a/phpstan.neon b/phpstan.neon index 38789a5..f14d2bc 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ includes: - - vendor/phpstan/phpstan/conf/config.levelmax.neon + - phar://phpstan.phar/conf/config.levelmax.neon - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/slam/phpstan-extensions/conf/slam-rules.neon - vendor/slam/phpstan-extensions/conf/thecodingmachine-rules.neon @@ -15,3 +15,4 @@ parameters: - '#Variable \$_\w+ in isset\(\) always exists and is not nullable#' - '#Parameter \#1 \$error_handler of function set_error_handler expects#' - '#Offset .(no_exception_thrown.*|undefined_index). does not exist on array#' + - '#Expression .+arrayPerVerificaErrori.+ on a separate line does not do anything#' diff --git a/tests/ErrorHandlerTest.php b/tests/ErrorHandlerTest.php index e406fde..de7c07a 100644 --- a/tests/ErrorHandlerTest.php +++ b/tests/ErrorHandlerTest.php @@ -11,23 +11,41 @@ final class ErrorHandlerTest extends TestCase { + /** + * @var string + */ private $backupErrorLog; + + /** + * @var string + */ private $errorLog; + + /** + * @var ErrorException + */ private $exception; - private $emailsSent; + + /** + * @var array> + */ + private $emailsSent = []; + + /** + * @var ErrorHandler + */ private $errorHandler; - protected function setUp() + protected function setUp(): void { \ini_set('display_errors', (string) false); - $this->backupErrorLog = \ini_get('error_log'); + $this->backupErrorLog = (string) \ini_get('error_log'); $this->errorLog = __DIR__ . \DIRECTORY_SEPARATOR . 'error_log_test'; \touch($this->errorLog); \ini_set('error_log', $this->errorLog); $this->exception = new ErrorException(\uniqid('normal_'), \E_USER_NOTICE); - $this->emailsSent = []; - $this->errorHandler = new ErrorHandler(function (string $subject, string $body) { + $this->errorHandler = new ErrorHandler(function (string $subject, string $body): void { $this->emailsSent[] = [ 'subject' => $subject, 'body' => $body, @@ -38,23 +56,23 @@ protected function setUp() $this->errorHandler->setTerminalWidth(50); } - protected function tearDown() + protected function tearDown(): void { \putenv('COLUMNS'); \ini_set('error_log', $this->backupErrorLog); @\unlink($this->errorLog); } - public function testDefaultConfiguration() + public function testDefaultConfiguration(): void { - $errorHandler = new ErrorHandler(function () { + $errorHandler = new ErrorHandler(function (): void { }); - static::assertTrue($errorHandler->isCli()); - static::assertTrue($errorHandler->autoExit()); - static::assertNotNull($errorHandler->getTerminalWidth()); - static::assertSame(\STDERR, $errorHandler->getErrorOutputStream()); - static::assertFalse($errorHandler->logErrors()); + self::assertTrue($errorHandler->isCli()); + self::assertTrue($errorHandler->autoExit()); + self::assertNotNull($errorHandler->getTerminalWidth()); + self::assertSame(\STDERR, $errorHandler->getErrorOutputStream()); + self::assertFalse($errorHandler->logErrors()); $errorHandler->setCli(false); $errorHandler->setAutoExit(false); @@ -62,21 +80,21 @@ public function testDefaultConfiguration() $errorHandler->setErrorOutputStream($memoryStream = \fopen('php://memory', 'r+')); $errorHandler->setLogErrors(true); - static::assertFalse($errorHandler->isCli()); - static::assertFalse($errorHandler->autoExit()); - static::assertSame($width, $errorHandler->getTerminalWidth()); - static::assertSame($memoryStream, $errorHandler->getErrorOutputStream()); - static::assertTrue($errorHandler->logErrors()); + self::assertFalse($errorHandler->isCli()); + self::assertFalse($errorHandler->autoExit()); + self::assertSame($width, $errorHandler->getTerminalWidth()); + self::assertSame($memoryStream, $errorHandler->getErrorOutputStream()); + self::assertTrue($errorHandler->logErrors()); $errorHandler->setErrorOutputStream(\uniqid('not_a_stream_')); - static::assertSame($memoryStream, $errorHandler->getErrorOutputStream()); + self::assertSame($memoryStream, $errorHandler->getErrorOutputStream()); } /** * @runInSeparateProcess * @preserveGlobalState disabled */ - public function testRegisterBuiltinHandlers() + public function testRegisterBuiltinHandlers(): void { $this->errorHandler->register(); $arrayPerVerificaErrori = []; @@ -91,15 +109,15 @@ public function testRegisterBuiltinHandlers() * @runInSeparateProcess * @preserveGlobalState disabled */ - public function testScream() + public function testScream(): void { $scream = [ \E_USER_WARNING => true, ]; - static::assertEmpty($this->errorHandler->getScreamSilencedErrors()); + self::assertEmpty($this->errorHandler->getScreamSilencedErrors()); $this->errorHandler->setScreamSilencedErrors($scream); - static::assertSame($scream, $this->errorHandler->getScreamSilencedErrors()); + self::assertSame($scream, $this->errorHandler->getScreamSilencedErrors()); $this->errorHandler->register(); @@ -112,20 +130,20 @@ public function testScream() @ \trigger_error($warningMessage, \E_USER_WARNING); } - public function testHandleCliException() + public function testHandleCliException(): void { $memoryStream = \fopen('php://memory', 'r+'); - static::assertIsResource($memoryStream); + self::assertIsResource($memoryStream); $this->errorHandler->setErrorOutputStream($memoryStream); $this->errorHandler->exceptionHandler($this->exception); \fseek($memoryStream, 0); - $output = \stream_get_contents($memoryStream); - static::assertContains($this->exception->getMessage(), $output); + $output = (string) \stream_get_contents($memoryStream); + self::assertStringContainsString($this->exception->getMessage(), $output); } - public function testHandleWebExceptionWithDisplay() + public function testHandleWebExceptionWithDisplay(): void { \ini_set('display_errors', (string) true); $this->errorHandler->setCli(false); @@ -133,15 +151,15 @@ public function testHandleWebExceptionWithDisplay() \ob_start(); $this->errorHandler->exceptionHandler($this->exception); - $output = \ob_get_clean(); + $output = (string) \ob_get_clean(); - static::assertContains($this->exception->getMessage(), $output); + self::assertStringContainsString($this->exception->getMessage(), $output); - $errorLogContent = \file_get_contents($this->errorLog); - static::assertContains($this->exception->getMessage(), $errorLogContent); + $errorLogContent = (string) \file_get_contents($this->errorLog); + self::assertStringContainsString($this->exception->getMessage(), $errorLogContent); } - public function testHandleWebExceptionWithoutDisplay() + public function testHandleWebExceptionWithoutDisplay(): void { \ini_set('display_errors', (string) false); $this->errorHandler->setCli(false); @@ -149,21 +167,21 @@ public function testHandleWebExceptionWithoutDisplay() \ob_start(); $this->errorHandler->exceptionHandler($this->exception); - $output = \ob_get_clean(); + $output = (string) \ob_get_clean(); - static::assertNotContains($this->exception->getMessage(), $output); + self::assertStringNotContainsString($this->exception->getMessage(), $output); - $errorLogContent = \file_get_contents($this->errorLog); - static::assertContains($this->exception->getMessage(), $errorLogContent); + $errorLogContent = (string) \file_get_contents($this->errorLog); + self::assertStringContainsString($this->exception->getMessage(), $errorLogContent); } - public function testLogErrorAndException() + public function testLogErrorAndException(): void { $this->errorHandler->setLogErrors(false); $this->errorHandler->logException($this->exception); - static::assertSame(0, \filesize($this->errorLog)); + self::assertSame(0, \filesize($this->errorLog)); $this->errorHandler->setLogErrors(true); @@ -171,19 +189,19 @@ public function testLogErrorAndException() $this->errorHandler->logException($exception); - $errorLogContent = \file_get_contents($this->errorLog); + $errorLogContent = (string) \file_get_contents($this->errorLog); - static::assertContains($exception->getMessage(), $errorLogContent); - static::assertContains($this->exception->getMessage(), $errorLogContent); + self::assertStringContainsString($exception->getMessage(), $errorLogContent); + self::assertStringContainsString($this->exception->getMessage(), $errorLogContent); } - public function testEmailException() + public function testEmailException(): void { $this->errorHandler->setLogErrors(false); $this->errorHandler->emailException($this->exception); - static::assertEmpty($this->emailsSent); + self::assertEmpty($this->emailsSent); $this->errorHandler->setLogErrors(true); @@ -193,21 +211,21 @@ public function testEmailException() $this->errorHandler->emailException($this->exception); - static::assertNotEmpty($this->emailsSent); + self::assertNotEmpty($this->emailsSent); $message = \current($this->emailsSent); - static::assertNotEmpty($message); + self::assertNotEmpty($message); $messageText = $message['body']; - static::assertContains($this->exception->getMessage(), $messageText); - static::assertContains($_SESSION[$key], $messageText); - static::assertContains($_POST[$key], $messageText); + self::assertStringContainsString($this->exception->getMessage(), $messageText); + self::assertStringContainsString($_SESSION[$key], $messageText); + self::assertStringContainsString($_POST[$key], $messageText); } - public function testCanHideVariablesFromEmail() + public function testCanHideVariablesFromEmail(): void { - static::assertTrue($this->errorHandler->logVariables()); + self::assertTrue($this->errorHandler->logVariables()); $this->errorHandler->setLogVariables(false); - static::assertFalse($this->errorHandler->logVariables()); + self::assertFalse($this->errorHandler->logVariables()); $this->errorHandler->setLogErrors(true); @@ -217,20 +235,20 @@ public function testCanHideVariablesFromEmail() $this->errorHandler->emailException($this->exception); - static::assertNotEmpty($this->emailsSent); + self::assertNotEmpty($this->emailsSent); $message = \current($this->emailsSent); - static::assertNotEmpty($message); + self::assertNotEmpty($message); $messageText = $message['body']; - static::assertContains($this->exception->getMessage(), $messageText); - static::assertNotContains($_SESSION[$key], $messageText); - static::assertNotContains($_POST[$key], $messageText); + self::assertStringContainsString($this->exception->getMessage(), $messageText); + self::assertStringNotContainsString($_SESSION[$key], $messageText); + self::assertStringNotContainsString($_POST[$key], $messageText); } - public function testErroriNellInvioDellaMailVengonoComunqueLoggati() + public function testErroriNellInvioDellaMailVengonoComunqueLoggati(): void { $mailError = \uniqid('mail_not_sent_'); - $mailCallback = static function () use ($mailError) { + $mailCallback = static function () use ($mailError): void { throw new ErrorException($mailError, \E_USER_ERROR); }; $errorHandler = new ErrorHandler($mailCallback); @@ -238,12 +256,12 @@ public function testErroriNellInvioDellaMailVengonoComunqueLoggati() $errorHandler->emailException($this->exception); - $errorLogContent = \file_get_contents($this->errorLog); - static::assertNotContains($this->exception->getMessage(), $errorLogContent); - static::assertContains($mailError, $errorLogContent); + $errorLogContent = (string) \file_get_contents($this->errorLog); + self::assertStringNotContainsString($this->exception->getMessage(), $errorLogContent); + self::assertStringContainsString($mailError, $errorLogContent); } - public function testUsernameInEmailSubject() + public function testUsernameInEmailSubject(): void { $username = \uniqid('bob_'); $_SESSION = ['custom_username_key' => $username]; @@ -253,25 +271,25 @@ public function testUsernameInEmailSubject() $message = \current($this->emailsSent); - static::assertContains($username, $message['subject']); + self::assertStringContainsString($username, $message['subject']); } - public function testTerminalWidthByEnv() + public function testTerminalWidthByEnv(): void { $width = \mt_rand(1000, 9000); \putenv(\sprintf('COLUMNS=%s', $width)); - $errorHandler = new ErrorHandler(function () { + $errorHandler = new ErrorHandler(function (): void { }); - static::assertSame($width, $errorHandler->getTerminalWidth()); + self::assertSame($width, $errorHandler->getTerminalWidth()); \putenv('COLUMNS'); - $errorHandler = new ErrorHandler(function () { + $errorHandler = new ErrorHandler(function (): void { }); $terminal = new Terminal(); - static::assertSame($terminal->getWidth(), $errorHandler->getTerminalWidth()); + self::assertSame($terminal->getWidth(), $errorHandler->getTerminalWidth()); } }