Skip to content

Commit

Permalink
Adding the ErrorLevel class
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Nov 25, 2023
1 parent 33bfbb5 commit 996e25f
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 81 deletions.
68 changes: 40 additions & 28 deletions src/Error/Cloak.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,25 @@

class Cloak
{
public const FORCE_NOTHING = 0;
public const SILENCE_ERROR = 1;
public const THROW_ON_ERROR = 2;
public const FOLLOW_ENV = 0;
public const BE_SILENT = 1;
public const THROW = 2;

protected static bool $useException = false;
protected ?ErrorException $exception = null;
protected readonly ErrorLevel $errorLevel;

public function __construct(
protected readonly Closure $closure,
protected readonly int $errorLevel = E_WARNING,
protected readonly int $behaviour = self::FORCE_NOTHING
ErrorLevel|int|null $errorLevel = null,
protected readonly int $onError = self::FOLLOW_ENV
) {
$errorLevel = $errorLevel ?? ErrorLevel::fromEnvironment();
if (!$errorLevel instanceof ErrorLevel) {
$errorLevel = ErrorLevel::new($errorLevel);
}

$this->errorLevel = $errorLevel;
}

public static function throwOnError(): void
Expand All @@ -42,44 +49,49 @@ public static function silenceError(): void
self::$useException = false;
}

public static function warning(Closure $closure, int $behaviour = self::FORCE_NOTHING): self
public static function default(Closure $closure, int $onError = self::FOLLOW_ENV): self
{
return new self(closure: $closure, onError: $onError);
}

public static function warning(Closure $closure, int $onError = self::FOLLOW_ENV): self
{
return new self($closure, E_WARNING, $behaviour);
return new self($closure, E_WARNING, $onError);
}

public static function notice(Closure $closure, int $behaviour = self::FORCE_NOTHING): self
public static function notice(Closure $closure, int $onError = self::FOLLOW_ENV): self
{
return new self($closure, E_NOTICE, $behaviour);
return new self($closure, E_NOTICE, $onError);
}

public static function deprecated(Closure $closure, int $behaviour = self::FORCE_NOTHING): self
public static function deprecated(Closure $closure, int $onError = self::FOLLOW_ENV): self
{
return new self($closure, E_DEPRECATED, $behaviour);
return new self($closure, E_DEPRECATED, $onError);
}

public static function strict(Closure $closure, int $behaviour = self::FORCE_NOTHING): self
public static function strict(Closure $closure, int $onError = self::FOLLOW_ENV): self
{
return new self($closure, E_STRICT, $behaviour);
return new self($closure, E_STRICT, $onError);
}

public static function userWarning(Closure $closure, int $behaviour = self::FORCE_NOTHING): self
public static function userWarning(Closure $closure, int $onError = self::FOLLOW_ENV): self
{
return new self($closure, E_USER_WARNING, $behaviour);
return new self($closure, E_USER_WARNING, $onError);
}

public static function userNotice(Closure $closure, int $behaviour = self::FORCE_NOTHING): self
public static function userNotice(Closure $closure, int $onError = self::FOLLOW_ENV): self
{
return new self($closure, E_USER_NOTICE, $behaviour);
return new self($closure, E_USER_NOTICE, $onError);
}

public static function userDeprecated(Closure $closure, int $behaviour = self::FORCE_NOTHING): self
public static function userDeprecated(Closure $closure, int $onError = self::FOLLOW_ENV): self
{
return new self($closure, E_USER_DEPRECATED, $behaviour);
return new self($closure, E_USER_DEPRECATED, $onError);
}

public static function all(Closure $closure, int $behaviour = self::FORCE_NOTHING): self
public static function all(Closure $closure, int $onError = self::FOLLOW_ENV): self
{
return new self($closure, E_ALL, $behaviour);
return new self($closure, E_ALL, $onError);
}

/**
Expand All @@ -98,19 +110,19 @@ public function __invoke(mixed ...$arguments): mixed
return true;
};

set_error_handler($errorHandler, $this->errorLevel);
set_error_handler($errorHandler, $this->errorLevel->toBytes());
$result = ($this->closure)(...$arguments);
restore_error_handler();

if (null === $this->exception) { /* @phpstan-ignore-line */
return $result;
}

if (self::THROW_ON_ERROR === $this->behaviour) { /* @phpstan-ignore-line */
if (self::THROW === $this->onError) { /* @phpstan-ignore-line */
throw $this->exception;
}

if (self::SILENCE_ERROR === $this->behaviour) {
if (self::BE_SILENT === $this->onError) {
return $result;
}

Expand All @@ -133,8 +145,8 @@ public function errorsAreSilenced(): bool

public function errorsAreThrown(): bool
{
return self::THROW_ON_ERROR === $this->behaviour
|| (self::SILENCE_ERROR !== $this->behaviour && true === self::$useException);
return self::THROW === $this->onError
|| (self::BE_SILENT !== $this->onError && true === self::$useException);
}

public function suppressAll(): bool
Expand Down Expand Up @@ -177,8 +189,8 @@ public function suppressUserDeprecated(): bool
return $this->suppress(E_USER_DEPRECATED);
}

public function suppress(int $errorLevel): bool
public function suppress(ErrorLevel|int $errorLevel): bool
{
return 0 !== ($errorLevel & $this->errorLevel);
return $this->errorLevel->contains($errorLevel);
}
}
27 changes: 3 additions & 24 deletions src/Error/CloakTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,33 +61,12 @@ public function testCapturesTriggeredError(): void

public function testCapturesSilencedError(): void
{
$lambda = Cloak::notice(function (string $x) {
@trigger_error($x);
});
$lambda = Cloak::warning(fn (string $x) => @trigger_error($x));
$lambda('foo');

self::assertNull($lambda->lastError());
}

public function testObjectDoesntInteractWithExistingErrorHandlers(): void
{
$count = 0;
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) use (&$count): bool {
$count++;

return true;
});
trigger_error('foo');
self::assertSame(1, $count);

$lambda = Cloak::warning(trigger_error(...));
$lambda('foo');
self::assertSame(1, $count); /* @phpstan-ignore-line */

trigger_error('foo');
self::assertSame(2, $count); /* @phpstan-ignore-line */
}

public function testErrorTransformedIntoARuntimeException(): void
{
$this->expectException(ErrorException::class);
Expand All @@ -110,7 +89,7 @@ public function testSpecificBehaviourOverrideGeneralErrorSetting(): void
{
Cloak::throwOnError();

$touch = Cloak::all(touch(...), Cloak::SILENCE_ERROR);
$touch = Cloak::all(touch(...), Cloak::BE_SILENT);
$touch('/foo');

self::assertInstanceOf(ErrorException::class, $touch->lastError());
Expand All @@ -130,7 +109,7 @@ public function it_can_detect_the_level_to_suppress(): void
$touch = new Cloak(
touch(...),
E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED,
Cloak::THROW_ON_ERROR
Cloak::THROW
);

self::assertTrue($touch->suppressAll());
Expand Down
135 changes: 135 additions & 0 deletions src/Error/ErrorLevel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

declare(strict_types=1);

namespace Bakame\Aide\Error;

use OutOfBoundsException;

use const E_ALL;
use const E_COMPILE_ERROR;
use const E_COMPILE_WARNING;
use const E_CORE_WARNING;
use const E_DEPRECATED;
use const E_ERROR;
use const E_NOTICE;
use const E_PARSE;
use const E_RECOVERABLE_ERROR;
use const E_STRICT;
use const E_USER_DEPRECATED;
use const E_USER_ERROR;
use const E_USER_NOTICE;
use const E_USER_WARNING;
use const E_WARNING;

final class ErrorLevel
{
private 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,
];

private function __construct(private readonly int $value)
{
}

public static function new(int $bytes = 0): self
{
return new self($bytes);
}

public static function fromEnvironment(): self
{
$errorReporting = error_reporting(-1);
error_reporting($errorReporting);

return new self($errorReporting);
}

public function toBytes(): int
{
return $this->value;
}

public function contains(self|int $level): bool
{
$level = $level instanceof self ? $level->toBytes() : $level;

return 0 !== ($level & $this->toBytes());
}

public function include(self|int|null ...$levels): self
{
$levels = array_map(fn (self|int|null $level) => match (true) {
$level instanceof self => $level->value,
default => $level,
}, $levels);

$levels = array_filter($levels, fn (?int $level) => null !== $level);
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 OutOfBoundsException('The error reporting level value `'.$level.'` is invalid.');
}

$value &= $level;
}

return self::new($value);
}

public function ignore(self|int|null ...$levels): self
{
$levels = array_map(fn (self|int|null $level) => match (true) {
$level instanceof self => $level->value,
default => $level,
}, $levels);

if (in_array(-1, $levels, true)) {
return self::new(0);
}

$levels = array_filter($levels, fn (int|null $level) => null !== $level && 0 !== $level && $this->value !== $level);
if ([] === $levels) {
return $this;
}

$value = $this->value;
foreach ($levels as $level) {
if (!in_array($level, self::LEVELS, true)) {
throw new OutOfBoundsException('The error reporting level value `'.$level.'` is invalid.');
}
$value &= ~$level;
}

return self::new($value);
}
}
Loading

0 comments on commit 996e25f

Please sign in to comment.