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 566cb41
Show file tree
Hide file tree
Showing 4 changed files with 312 additions and 124 deletions.
106 changes: 61 additions & 45 deletions src/Error/Cloak.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
use Closure;
use ErrorException;

use function error_reporting;
use function restore_error_handler;
use function set_error_handler;

use const E_ALL;
use const E_DEPRECATED;
use const E_NOTICE;
Expand All @@ -18,68 +22,80 @@

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 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
protected readonly int $onError = self::FOLLOW_ENV,
ErrorLevel|int|null $errorLevel = null
) {
$errorLevel = $errorLevel ?? ErrorLevel::fromEnvironment();
if (!$errorLevel instanceof ErrorLevel) {
$errorLevel = ErrorLevel::new($errorLevel);
}

$this->errorLevel = $errorLevel;
}

public static function throwOnError(): void
{
self::$useException = true;
}

public static function silenceError(): void
public static function silentOnError(): void
{
self::$useException = false;
}

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

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

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, $onError, E_NOTICE);
}

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, $onError, E_DEPRECATED);
}

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, $onError, E_STRICT);
}

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, $onError, E_USER_WARNING);
}

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, $onError, E_USER_NOTICE);
}

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, $onError, E_USER_DEPRECATED);
}

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, $onError, E_ALL);
}

/**
Expand All @@ -98,19 +114,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::SILENT === $this->onError) {
return $result;
}

Expand All @@ -133,52 +149,52 @@ 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::SILENT !== $this->onError && true === self::$useException);
}

public function suppressAll(): bool
public function includeAll(): bool
{
return $this->suppress(E_ALL);
return $this->include(E_ALL);
}

public function suppressWarning(): bool
public function includeWarning(): bool
{
return $this->suppress(E_WARNING);
return $this->include(E_WARNING);
}

public function suppressNotice(): bool
public function includeNotice(): bool
{
return $this->suppress(E_NOTICE);
return $this->include(E_NOTICE);
}

public function suppressDeprecated(): bool
public function includeDeprecated(): bool
{
return $this->suppress(E_DEPRECATED);
return $this->include(E_DEPRECATED);
}

public function suppressStrict(): bool
public function includeStrict(): bool
{
return $this->suppress(E_STRICT);
return $this->include(E_STRICT);
}

public function suppressUserWarning(): bool
public function includeUserWarning(): bool
{
return $this->suppress(E_USER_WARNING);
return $this->include(E_USER_WARNING);
}

public function suppressUserNotice(): bool
public function includeUserNotice(): bool
{
return $this->suppress(E_USER_NOTICE);
return $this->include(E_USER_NOTICE);
}

public function suppressUserDeprecated(): bool
public function includeUserDeprecated(): bool
{
return $this->suppress(E_USER_DEPRECATED);
return $this->include(E_USER_DEPRECATED);
}

public function suppress(int $errorLevel): bool
public function include(ErrorLevel|int $errorLevel): bool
{
return 0 !== ($errorLevel & $this->errorLevel);
return $this->errorLevel->contains($errorLevel);
}
}
55 changes: 17 additions & 38 deletions src/Error/CloakTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ protected function setUp(): void
{
parent::setUp();

Cloak::silenceError();
Cloak::silentOnError();
}

#[Test]
Expand All @@ -29,13 +29,13 @@ public function it_returns_information_about_its_error_reporting_level(): void
$res = $lambda('/foo');

self::assertFalse($res);
self::assertTrue($lambda->suppressWarning());
self::assertFalse($lambda->suppressNotice());
self::assertTrue($lambda->includeWarning());
self::assertFalse($lambda->includeNotice());
self::assertInstanceOf(ErrorException::class, $lambda->lastError());
}

#[Test]
public function it_will_suppress_nothing_in_case_of_success(): void
public function it_will_include_nothing_in_case_of_success(): void
{
$lambda = Cloak::userWarning(strtoupper(...));
$res = $lambda('foo');
Expand All @@ -48,7 +48,7 @@ public function testGetErrorReporting(): void
{
$lambda = Cloak::deprecated(strtoupper(...));

self::assertTrue($lambda->suppressDeprecated());
self::assertTrue($lambda->includeDeprecated());
}

public function testCapturesTriggeredError(): void
Expand All @@ -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::SILENT);
$touch('/foo');

self::assertInstanceOf(ErrorException::class, $touch->lastError());
Expand All @@ -125,21 +104,21 @@ public function testCaptureNothingThrowNoException(): void
}

#[Test]
public function it_can_detect_the_level_to_suppress(): void
public function it_can_detect_the_level_to_include(): void
{
$touch = new Cloak(
touch(...),
E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED,
Cloak::THROW_ON_ERROR
Cloak::THROW,
E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
);

self::assertTrue($touch->suppressAll());
self::assertFalse($touch->suppressStrict());
self::assertFalse($touch->suppressDeprecated());
self::assertFalse($touch->suppressNotice());
self::assertTrue($touch->suppressUserNotice());
self::assertTrue($touch->suppressUserDeprecated());
self::assertTrue($touch->suppressUserWarning());
self::assertTrue($touch->includeAll());
self::assertFalse($touch->includeStrict());
self::assertFalse($touch->includeDeprecated());
self::assertFalse($touch->includeNotice());
self::assertTrue($touch->includeUserNotice());
self::assertTrue($touch->includeUserDeprecated());
self::assertTrue($touch->includeUserWarning());
self::assertTrue($touch->errorsAreThrown());
self::assertFalse($touch->errorsAreSilenced());
}
Expand Down
Loading

0 comments on commit 566cb41

Please sign in to comment.