feature #33155 [ErrorHandler] Added call() method utility to turns any PHP error into \ErrorException (yceruto)
This PR was merged into the 4.4 branch.
Discussion
----------
[ErrorHandler] Added call() method utility to turns any PHP error into \ErrorException
| Q | A
| ------------- | ---
| Branch? | 4.4
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | https://github.com/symfony/symfony/issues/32936
| License | MIT
| Doc PR | symfony/symfony-docs#...
**Issue**
There is no easy way to catch PHP warnings, though some progress has been made in this area for PHP 8.0 (https://wiki.php.net/rfc/consistent_type_errors).
**Before**
```php
$file = file_get_contents('unknown.txt');
// PHP Warning: file_get_contents(unknown.txt): failed to open stream: No such file or directory
// workaround:
$file = @file_get_contents('unknown.txt');
if (false === $file) {
$e = error_get_last();
throw new \ErrorException($e['message'], 0, $e['type'], $e['file'], $e['line']);
}
```
**After**
```php
$file = ErrorHandler::call('file_get_contents', 'unknown.txt');
// or
$file = ErrorHandler::call(static function () {
return file_get_contents('unknown.txt');
});
// or (PHP 7.4)
$file = ErrorHandler::call(fn () => file_get_contents('unknown.txt'));
```
All credits to @nicolas-grekas https://github.com/symfony/symfony/issues/32936#issuecomment-518152681 and @vudaltsov for the idea.
Commits
-------
0faa855b5e
Added ErrorHandler::call() method utility to turns any PHP warnings into `\ErrorException`
This commit is contained in:
commit
98e86816ad
@ -5,3 +5,4 @@ CHANGELOG
|
|||||||
-----
|
-----
|
||||||
|
|
||||||
* added the component
|
* added the component
|
||||||
|
* added `ErrorHandler::call()` method utility to turn any PHP error into `\ErrorException`
|
||||||
|
@ -153,6 +153,32 @@ class ErrorHandler
|
|||||||
return $handler;
|
return $handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls a function and turns any PHP error into \ErrorException.
|
||||||
|
*
|
||||||
|
* @return mixed What $function(...$arguments) returns
|
||||||
|
*
|
||||||
|
* @throws \ErrorException When $function(...$arguments) triggers a PHP error
|
||||||
|
*/
|
||||||
|
public static function call(callable $function, ...$arguments)
|
||||||
|
{
|
||||||
|
set_error_handler(static function (int $type, string $message, string $file, int $line) {
|
||||||
|
if (__FILE__ === $file) {
|
||||||
|
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
|
||||||
|
$file = $trace[2]['file'] ?? $file;
|
||||||
|
$line = $trace[2]['line'] ?? $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \ErrorException($message, 0, $type, $file, $line);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
return $function(...$arguments);
|
||||||
|
} finally {
|
||||||
|
restore_error_handler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct(BufferingLogger $bootstrappingLogger = null)
|
public function __construct(BufferingLogger $bootstrappingLogger = null)
|
||||||
{
|
{
|
||||||
if ($bootstrappingLogger) {
|
if ($bootstrappingLogger) {
|
||||||
|
@ -123,11 +123,62 @@ class ErrorHandlerTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
// dummy function to test trace in error handler.
|
// dummy function to test trace in error handler.
|
||||||
private static function triggerNotice($that)
|
public static function triggerNotice($that)
|
||||||
{
|
{
|
||||||
$that->assertSame('', $foo.$foo.$bar);
|
$that->assertSame('', $foo.$foo.$bar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testFailureCall()
|
||||||
|
{
|
||||||
|
$this->expectException(\ErrorException::class);
|
||||||
|
$this->expectExceptionMessage('fopen(unknown.txt): failed to open stream: No such file or directory');
|
||||||
|
|
||||||
|
ErrorHandler::call('fopen', 'unknown.txt', 'r');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCallRestoreErrorHandler()
|
||||||
|
{
|
||||||
|
$prev = set_error_handler('var_dump');
|
||||||
|
try {
|
||||||
|
ErrorHandler::call('fopen', 'unknown.txt', 'r');
|
||||||
|
$this->fail('An \ErrorException should have been raised');
|
||||||
|
} catch (\ErrorException $e) {
|
||||||
|
$prev = set_error_handler($prev);
|
||||||
|
restore_error_handler();
|
||||||
|
} finally {
|
||||||
|
restore_error_handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertSame('var_dump', $prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCallErrorExceptionInfo()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ErrorHandler::call([self::class, 'triggerNotice'], $this);
|
||||||
|
$this->fail('An \ErrorException should have been raised');
|
||||||
|
} catch (\ErrorException $e) {
|
||||||
|
$trace = $e->getTrace();
|
||||||
|
$this->assertSame(E_NOTICE, $e->getSeverity());
|
||||||
|
$this->assertSame(__FILE__, $e->getFile());
|
||||||
|
$this->assertSame('Undefined variable: foo', $e->getMessage());
|
||||||
|
$this->assertSame(0, $e->getCode());
|
||||||
|
$this->assertSame('Symfony\Component\ErrorHandler\{closure}', $trace[0]['function']);
|
||||||
|
$this->assertSame(ErrorHandler::class, $trace[0]['class']);
|
||||||
|
$this->assertSame('triggerNotice', $trace[1]['function']);
|
||||||
|
$this->assertSame(__CLASS__, $trace[1]['class']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSuccessCall()
|
||||||
|
{
|
||||||
|
touch($filename = tempnam(sys_get_temp_dir(), 'sf_error_handler_'));
|
||||||
|
|
||||||
|
self::assertIsResource(ErrorHandler::call('fopen', $filename, 'r'));
|
||||||
|
|
||||||
|
unlink($filename);
|
||||||
|
}
|
||||||
|
|
||||||
public function testConstruct()
|
public function testConstruct()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
Reference in New Issue
Block a user