diff --git a/src/Symfony/Component/ErrorHandler/CHANGELOG.md b/src/Symfony/Component/ErrorHandler/CHANGELOG.md index 094072510d707..c7c245a4399f2 100644 --- a/src/Symfony/Component/ErrorHandler/CHANGELOG.md +++ b/src/Symfony/Component/ErrorHandler/CHANGELOG.md @@ -5,3 +5,4 @@ CHANGELOG ----- * added the component + * added `ErrorHandler::call()` method utility to turn any PHP error into `\ErrorException` diff --git a/src/Symfony/Component/ErrorHandler/ErrorHandler.php b/src/Symfony/Component/ErrorHandler/ErrorHandler.php index 14ab7022faae0..2b0d1184fe4fc 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorHandler.php +++ b/src/Symfony/Component/ErrorHandler/ErrorHandler.php @@ -153,6 +153,32 @@ public static function register(self $handler = null, bool $replace = true): sel 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) { if ($bootstrappingLogger) { diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php index c087aed2f3d8d..6f8f56ef69734 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php @@ -123,11 +123,62 @@ public function testNotice() } // dummy function to test trace in error handler. - private static function triggerNotice($that) + public static function triggerNotice($that) { $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() { try {
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: