From c031516873411a3e001cf7597950bded89a417a6 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Sun, 26 Nov 2023 21:52:16 +0100 Subject: [PATCH] Improve ErrorLevel class --- src/Error/Cloak.php | 45 ++++------- src/Error/CloakTest.php | 2 +- src/Error/ErrorLevel.php | 142 +++++++++++++-------------------- src/Error/ErrorLevelTest.php | 149 +++++++++++++++++++++++++++++++++++ src/Error/README.md | 21 +++-- 5 files changed, 231 insertions(+), 128 deletions(-) create mode 100644 src/Error/ErrorLevelTest.php diff --git a/src/Error/Cloak.php b/src/Error/Cloak.php index bcc3a2f..ef89b0f 100644 --- a/src/Error/Cloak.php +++ b/src/Error/Cloak.php @@ -11,15 +11,6 @@ use function restore_error_handler; use function set_error_handler; -use const E_ALL; -use const E_DEPRECATED; -use const E_NOTICE; -use const E_STRICT; -use const E_USER_DEPRECATED; -use const E_USER_NOTICE; -use const E_USER_WARNING; -use const E_WARNING; - class Cloak { public const FOLLOW_ENV = 0; @@ -33,14 +24,15 @@ class Cloak public function __construct( protected readonly Closure $closure, protected readonly int $onError = self::FOLLOW_ENV, - ErrorLevel|int|null $errorLevel = null + ErrorLevel|string|int|null $errorLevel = null ) { - $errorLevel = $errorLevel ?? ErrorLevel::fromEnvironment(); - if (!$errorLevel instanceof ErrorLevel) { - $errorLevel = ErrorLevel::new($errorLevel); - } + $this->errorLevel = match (true) { + $errorLevel instanceof ErrorLevel => $errorLevel, + is_string($errorLevel) => ErrorLevel::fromName($errorLevel), + is_int($errorLevel) => ErrorLevel::fromValue($errorLevel), + default => ErrorLevel::fromEnvironment(), + }; - $this->errorLevel = $errorLevel; $this->errors = new CloakedErrors(); } @@ -56,47 +48,42 @@ public static function silentOnError(): void public static function fromEnvironment(Closure $closure, int $onError = self::FOLLOW_ENV): self { - return new self(closure: $closure, onError: $onError, errorLevel: $onError); + return new self($closure, $onError); } public static function warning(Closure $closure, int $onError = self::FOLLOW_ENV): self { - return new self($closure, $onError, E_WARNING); + return new self($closure, $onError, 'E_WARNING'); } public static function notice(Closure $closure, int $onError = self::FOLLOW_ENV): self { - return new self($closure, $onError, E_NOTICE); + return new self($closure, $onError, 'E_NOTICE'); } public static function deprecated(Closure $closure, int $onError = self::FOLLOW_ENV): self { - return new self($closure, $onError, E_DEPRECATED); - } - - public static function strict(Closure $closure, int $onError = self::FOLLOW_ENV): self - { - return new self($closure, $onError, E_STRICT); + return new self($closure, $onError, 'E_DEPRECATED'); } public static function userWarning(Closure $closure, int $onError = self::FOLLOW_ENV): self { - return new self($closure, $onError, E_USER_WARNING); + return new self($closure, $onError, 'E_USER_WARNING'); } public static function userNotice(Closure $closure, int $onError = self::FOLLOW_ENV): self { - return new self($closure, $onError, E_USER_NOTICE); + return new self($closure, $onError, 'E_USER_NOTICE'); } public static function userDeprecated(Closure $closure, int $onError = self::FOLLOW_ENV): self { - return new self($closure, $onError, E_USER_DEPRECATED); + return new self($closure, $onError, 'E_USER_DEPRECATED'); } public static function all(Closure $closure, int $onError = self::FOLLOW_ENV): self { - return new self($closure, $onError, E_ALL); + return new self($closure, $onError, 'E_ALL'); } /** @@ -119,7 +106,7 @@ public function __invoke(mixed ...$arguments): mixed }; try { - set_error_handler($errorHandler, $this->errorLevel->toBytes()); + set_error_handler($errorHandler, $this->errorLevel->value()); $result = ($this->closure)(...$arguments); } finally { restore_error_handler(); diff --git a/src/Error/CloakTest.php b/src/Error/CloakTest.php index 3e9d74e..2543c28 100644 --- a/src/Error/CloakTest.php +++ b/src/Error/CloakTest.php @@ -98,7 +98,7 @@ public function testSpecificBehaviourOverrideGeneralErrorSetting(): void public function testCaptureNothingThrowNoException(): void { Cloak::throwOnError(); - $strtoupper = Cloak::strict(strtoupper(...)); + $strtoupper = Cloak::notice(strtoupper(...)); self::assertSame('FOO', $strtoupper('foo')); } diff --git a/src/Error/ErrorLevel.php b/src/Error/ErrorLevel.php index a6ae8c7..1668d3e 100644 --- a/src/Error/ErrorLevel.php +++ b/src/Error/ErrorLevel.php @@ -4,13 +4,11 @@ namespace Bakame\Aide\Error; -use OutOfRangeException; use ValueError; -use function array_filter; -use function array_map; +use function array_key_exists; +use function array_search; use function error_reporting; -use function in_array; use const E_ALL; use const E_COMPILE_ERROR; @@ -31,24 +29,22 @@ class ErrorLevel { protected const LEVELS = [ - -1, - 0, - E_ERROR, - E_WARNING, - E_PARSE, - E_NOTICE, - E_CORE_ERROR, - E_CORE_WARNING, - E_COMPILE_ERROR, - E_COMPILE_WARNING, - E_RECOVERABLE_ERROR, - E_ALL, - E_DEPRECATED, - E_STRICT, - E_USER_ERROR, - E_USER_WARNING, - E_USER_NOTICE, - E_USER_DEPRECATED, + E_ALL => 'E_ALL', + E_ERROR => 'E_ERROR', + E_WARNING => 'E_WARNING', + E_PARSE => 'E_PARSE', + E_NOTICE => 'E_NOTICE', + E_CORE_ERROR => 'E_CORE_ERROR', + E_CORE_WARNING => 'E_CORE_WARNING', + E_COMPILE_ERROR => 'E_COMPILE_ERROR', + E_COMPILE_WARNING => 'E_COMPILE_WARNING', + E_USER_ERROR => 'E_USER_ERROR', + E_USER_WARNING => 'E_USER_WARNING', + E_USER_NOTICE => 'E_USER_NOTICE', + E_STRICT => 'E_STRICT', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + E_DEPRECATED => 'E_DEPRECATED', + E_USER_DEPRECATED => 'E_USER_DEPRECATED', ]; private function __construct(protected readonly int $value) @@ -58,99 +54,73 @@ private function __construct(protected readonly int $value) } } - public static function new(int $bytes = 0): self + public static function fromValue(int $value): self { - return new self($bytes); + return new self($value); } - public static function fromEnvironment(): self + public static function fromName(string $name): self { - $errorReporting = error_reporting(-1); - error_reporting($errorReporting); + /** @var int|false $errorLevel */ + $errorLevel = array_search($name, self::LEVELS, true); + if (false === $errorLevel) { + throw new ValueError('The name `'.$name.'` is invalid or unknown error reporting level name.'); + } - return new self($errorReporting); + return new self($errorLevel); } - public function toBytes(): int + public static function fromEnvironment(): self { - return $this->value; + return new self(error_reporting()); } - public function contains(self|int ...$levels): bool + public static function fromExclusion(string|int ...$levels): self { - if (-1 === $this->value) { - return true; - } - - if (0 === $this->value) { - return false; - } - - foreach ($levels as $level) { - $level = $level instanceof self ? $level->value : $level; - if (0 !== ($level & $this->value)) { - return true; + return new self(array_reduce($levels, function (int $carry, string|int $level) { + $errorLevel = is_string($level) ? self::fromName($level)->value : $level; + if (!array_key_exists($errorLevel, self::LEVELS)) { + throw new ValueError('The value `'.$level.'` is invalid as a error reporting level value in PHP.'); } - } - return false; + return $carry & ~$errorLevel; + }, E_ALL)); } - public function include(self|int ...$levels): self + public static function fromInclusion(string|int ...$levels): self { - $levels = array_map(fn (self|int $level) => match (true) { - $level instanceof self => $level->value, - default => $level, - }, $levels); - - if ([] === $levels) { - return $this; - } - - if (in_array(-1, $levels, true)) { - return self::new(-1); - } - - if (in_array(0, $levels, true)) { - return self::new(0); - } - - $value = 0 === $this->value ? $levels[0] : $this->value; - foreach ($levels as $level) { - if (!in_array($level, self::LEVELS, true)) { - throw new OutOfRangeException('The error reporting level value `'.$level.'` is invalid.'); + return new self(array_reduce($levels, function (int $carry, string|int $level) { + $errorLevel = is_string($level) ? self::fromName($level)->value : $level; + if (!array_key_exists($errorLevel, self::LEVELS)) { + throw new ValueError('The value `'.$level.'` is invalid as a error reporting level value in PHP.'); } - $value &= $level; - } - - return self::new($value); + return $carry | $errorLevel; + }, 0)); } - public function ignore(self|int ...$levels): self + public function value(): int { - $levels = array_map(fn (self|int $level) => match (true) { - $level instanceof self => $level->value, - default => $level, - }, $levels); + return $this->value; + } - if (in_array(-1, $levels, true)) { - return self::new(0); + public function contains(self|int ...$levels): bool + { + if ([] === $levels || 0 === $this->value) { + return false; } - $levels = array_filter($levels, fn (int $level) => 0 !== $level && $this->value !== $level); - if ([] === $levels) { - return $this; + if (-1 === $this->value) { + return true; } - $value = $this->value; foreach ($levels as $level) { - if (!in_array($level, self::LEVELS, true)) { - throw new OutOfRangeException('The error reporting level value `'.$level.'` is invalid.'); + $level = $level instanceof self ? $level->value : $level; + if (1 > $level || $level !== ($this->value & $level)) { + return false; } - $value &= ~$level; } - return self::new($value); + return true; } } diff --git a/src/Error/ErrorLevelTest.php b/src/Error/ErrorLevelTest.php new file mode 100644 index 0000000..1e6ca84 --- /dev/null +++ b/src/Error/ErrorLevelTest.php @@ -0,0 +1,149 @@ +contains($test)); + } + + /** + * @return iterable + */ + public static function provideErrorLevelContains(): iterable + { + yield '-1 contains everything (1)' => [ + 'errorLevel' => -1, + 'test' => 0, + 'expected' => true, + ]; + + yield '-1 contains everything (2)' => [ + 'errorLevel' => -1, + 'test' => E_ALL | E_WARNING, + 'expected' => true, + ]; + + yield '0 contains nothing (1)' => [ + 'errorLevel' => 0, + 'test' => -1, + 'expected' => false, + ]; + + yield '0 contains nothing (2)' => [ + 'errorLevel' => 0, + 'test' => E_DEPRECATED | E_STRICT, + 'expected' => false, + ]; + + yield 'union error level (1)' => [ + 'errorLevel' => E_DEPRECATED | E_STRICT, + 'test' => E_DEPRECATED, + 'expected' => true, + ]; + + yield 'union error level (2)' => [ + 'errorLevel' => E_WARNING | E_NOTICE, + 'test' => E_NOTICE, + 'expected' => true, + ]; + + yield 'exclusion error level (1)' => [ + 'errorLevel' => E_ALL & ~E_NOTICE, + 'test' => E_WARNING, + 'expected' => true, + ]; + + yield 'exclusion error level (2)' => [ + 'errorLevel' => E_ALL & ~E_WARNING, + 'test' => E_WARNING, + 'expected' => false, + ]; + } + + #[Test] + public function it_can_create_error_level_by_exclusion(): void + { + self::assertSame( + ErrorLevel::fromExclusion(E_WARNING, 'E_WARNING')->value(), + ErrorLevel::fromValue(E_ALL & ~E_WARNING)->value(), + ); + } + + #[Test] + public function it_can_create_error_level_from_environment(): void + { + self::assertSame( + ErrorLevel::fromEnvironment()->value(), + ErrorLevel::fromValue(error_reporting())->value(), + ); + } + + #[Test] + public function it_can_create_error_level_by_inclusion(): void + { + self::assertSame( + ErrorLevel::fromInclusion(E_WARNING, 'E_WARNING')->value(), + ErrorLevel::fromValue(E_WARNING)->value(), + ); + } + + #[Test] + public function it_fails_to_create_error_level_by_exclusion(): void + { + $this->expectException(ValueError::class); + + ErrorLevel::fromExclusion(-2, 'E_WARNING')->value(); + } + + #[Test] + public function it_fails_to_create_error_level_by_inclusion(): void + { + $this->expectException(ValueError::class); + + ErrorLevel::fromInclusion(-2, 'E_FOOBAR')->value(); + } + + #[Test] + public function it_can_create_error_level_by_name(): void + { + self::assertSame( + ErrorLevel::fromName('E_WARNING')->value(), + ErrorLevel::fromValue(E_WARNING)->value(), + ); + } + + #[Test] + public function it_fails_to_create_error_level_by_name(): void + { + $this->expectException(ValueError::class); + + ErrorLevel::fromName('E_FOOBAR'); + } + + #[Test] + public function it_fails_to_create_error_level_by_value(): void + { + $this->expectException(ValueError::class); + + ErrorLevel::fromValue(-2); + } +} diff --git a/src/Error/README.md b/src/Error/README.md index c8fed85..a98efcc 100644 --- a/src/Error/README.md +++ b/src/Error/README.md @@ -140,7 +140,6 @@ Cloak::all(); Cloak::warning(); Cloak::notice(); Cloak::deprecated(); -Cloak::strict(); Cloak::userWarning(); Cloak::userNotice(); Cloak::userDeprecated(); @@ -183,17 +182,18 @@ use Bakame\Aide\Error\ErrorLevel; $touch = new Cloak( touch(...), Cloak::THROW, - ErrorLevel::new(E_ALL)->ignore(E_NOTICE, E_STRICT, E_DEPRECATED) + ErrorLevel::fromExclusion(E_NOTICE, E_STRICT, E_DEPRECATED) ); ``` The class contains five (5) methods to ease working with error reporting level: -`ErrorLevel::new` allow instantiating the class with any value you want. Alternatively, you can +`ErrorLevel::fromValue` allow instantiating the class with any value you want. Alternatively, you can instantiate the class to match your current environment settings using `ErrorLevel::fromEnvironment`. - -To ignore or include error reporting level just use the `include` or `exclude` nethods which returns a -new object on each different value given. +`ErrorLevel::fromInclusion` instantiate the error level by adding all the submitted values via a +bitwise `OR` operation starting at `0` meaning that no Error reporting level exists if none is added. +Conversely `ErrorLevel::fromExclusion` does the opposite, each value given will be remove from the +maximum value `E_ALL`. Last but not least you can tell which error reporting is being configured using the `contains` method. @@ -206,12 +206,9 @@ ErrorLevel::fromEnvironment()->contains(E_STRICT); // returns true if the current value in error_reporting contains `E_STRICT` // returns false otherwise. - $errorLevel = ErrorLevel::new() - ->include(E_ALL, E_USER_ERROR) - ->ignore(E_NOTICE, E_STRICT, E_DEPRECATED) - ->toBytes(); -// `toBytes` returns the int value corresponding to the calculated error level. -// as per PHP docs this value may differ per PHP version. + $errorLevel = ErrorLevel::fromExclusion(E_NOTICE, E_DEPRECATED)->value(); +// `value` returns the int value corresponding to the calculated error level. +// the errorLevel calculated will ignore notice, and deprecated error. ``` ## Credits