Skip to content

Commit

Permalink
fixed throwing errors in exists-check functions (#54)
Browse files Browse the repository at this point in the history
Suppresses warning triggered by `stat`/`lstat` when file does not exit.

By injecting another error handler which resets error reporting the
suppressed warning gets caught and does not appear in error logs.
  • Loading branch information
oruborus authored May 16, 2024
1 parent ed47c5f commit 8616a09
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 3 deletions.
11 changes: 8 additions & 3 deletions src/NativeWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,19 @@ public function unlink(string $path): bool

public function url_stat(string $path, int $flags)
{
if ($flags & STREAM_URL_STAT_QUIET) {
set_error_handler(static fn (): bool => true);
}
try {
$func = $flags & STREAM_URL_STAT_LINK ? 'lstat' : 'stat';
return $flags & STREAM_URL_STAT_QUIET
? @$this->native($func, $path)
: $this->native($func, $path);
return $this->native($func, $path);
} catch (\RuntimeException $e) {
// SplFileInfo::isFile throws exception
return false;
} finally {
if ($flags & STREAM_URL_STAT_QUIET) {
restore_error_handler();
}
}
}

Expand Down
101 changes: 101 additions & 0 deletions tests/BypassFinals/issue52.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

/**
* @see https://github.com/dg/bypass-finals/issues/52
* @see https://www.php.net/manual/en/language.operators.errorcontrol.php
*
* As certain testing frameworks establish error handlers to pick up
* on suppressed errors, exceptions and warnings, the warning generated
* by `stat`/`lstat` when provided an unknown path is recorded.
*
* `nette/tester` does _not_ detect suppressed warnings as the error handler
* is set right before executing the closure.
*/

declare(strict_types=1);

use Tester\Assert;

require __DIR__ . '/../../vendor/autoload.php';

Tester\Environment::setup();

DG\BypassFinals::enable();

function thoroughErrorHandler(int $number, string $message, string $file, int $line): bool
{
/**
* NOTE: The use of the `@` operator sets the error level to
* `E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE`
* or 0 prior to PHP 8.0.0.
* Resetting the error level to `E_ALL | E_STRICT` lets the previous error handler detect
* suppressed errors, exceptions or warnings.
*/
error_reporting(E_ALL | E_STRICT);

/**
* NOTE: Acquire the previous error handler from the stack and forward the error.
* Restore the error handler stack afterwards.
*/
restore_error_handler();
$errorHandler = set_error_handler(null);
$errorHandler($number, $message, $file, $line);

return true;
}

Assert::noError(function () {
set_error_handler('thoroughErrorHandler');

file_exists('unknown');

restore_error_handler();
});

Assert::noError(function () {
set_error_handler('thoroughErrorHandler');

is_writable('unknown');

restore_error_handler();
});

Assert::noError(function () {
set_error_handler('thoroughErrorHandler');

is_readable('unknown');

restore_error_handler();
});

Assert::noError(function () {
set_error_handler('thoroughErrorHandler');

is_executable('unknown');

restore_error_handler();
});

Assert::noError(function () {
set_error_handler('thoroughErrorHandler');

is_file('unknown');

restore_error_handler();
});

Assert::noError(function () {
set_error_handler('thoroughErrorHandler');

is_dir('unknown');

restore_error_handler();
});

Assert::noError(function () {
set_error_handler('thoroughErrorHandler');

is_link('unknown');

restore_error_handler();
});

0 comments on commit 8616a09

Please sign in to comment.