diff --git a/src/functions.php b/src/functions.php index cad88a2..865f6b9 100644 --- a/src/functions.php +++ b/src/functions.php @@ -104,13 +104,27 @@ */ function append($stream, $callback, $read_write = STREAM_FILTER_ALL) { - $ret = @\stream_filter_append($stream, register(), $read_write, $callback); + $errstr = ''; + \set_error_handler(function ($_, $error) use (&$errstr) { + // Match errstr from PHP's warning message. + // stream_filter_append() expects parameter 1 to be resource,... + $errstr = $error; // @codeCoverageIgnore + }); + + try { + $ret = \stream_filter_append($stream, register(), $read_write, $callback); + } catch (\TypeError $e) { // @codeCoverageIgnoreStart + // Throws TypeError on PHP 8.0+ + \restore_error_handler(); + throw $e; + } // @codeCoverageIgnoreEnd + + \restore_error_handler(); // PHP 8 throws above on type errors, older PHP and memory issues can throw here // @codeCoverageIgnoreStart if ($ret === false) { - $error = \error_get_last() + array('message' => ''); - throw new \RuntimeException('Unable to append filter: ' . $error['message']); + throw new \RuntimeException('Unable to append filter: ' . $errstr); } // @codeCoverageIgnoreEnd @@ -147,13 +161,27 @@ function append($stream, $callback, $read_write = STREAM_FILTER_ALL) */ function prepend($stream, $callback, $read_write = STREAM_FILTER_ALL) { - $ret = @\stream_filter_prepend($stream, register(), $read_write, $callback); + $errstr = ''; + \set_error_handler(function ($_, $error) use (&$errstr) { + // Match errstr from PHP's warning message. + // stream_filter_prepend() expects parameter 1 to be resource,... + $errstr = $error; // @codeCoverageIgnore + }); + + try { + $ret = \stream_filter_prepend($stream, register(), $read_write, $callback); + } catch (\TypeError $e) { // @codeCoverageIgnoreStart + // Throws TypeError on PHP 8.0+ + \restore_error_handler(); + throw $e; + } // @codeCoverageIgnoreEnd + + \restore_error_handler(); // PHP 8 throws above on type errors, older PHP and memory issues can throw here // @codeCoverageIgnoreStart if ($ret === false) { - $error = \error_get_last() + array('message' => ''); - throw new \RuntimeException('Unable to prepend filter: ' . $error['message']); + throw new \RuntimeException('Unable to prepend filter: ' . $errstr); } // @codeCoverageIgnoreEnd @@ -242,16 +270,25 @@ function prepend($stream, $callback, $read_write = STREAM_FILTER_ALL) function fun($filter, $parameters = null) { $fp = \fopen('php://memory', 'w'); + + $errstr = ''; + \set_error_handler(function ($_, $error) use (&$errstr) { + // Match errstr from PHP's warning message. + // stream_filter_append() expects parameter 1 to be resource,... + $errstr = $error; + }); + if (\func_num_args() === 1) { - $filter = @\stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE); + $filter = \stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE); } else { - $filter = @\stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE, $parameters); + $filter = \stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE, $parameters); } + \restore_error_handler(); + if ($filter === false) { \fclose($fp); - $error = \error_get_last() + array('message' => ''); - throw new \RuntimeException('Unable to access built-in filter: ' . $error['message']); + throw new \RuntimeException('Unable to access built-in filter: ' . $errstr); } // append filter function which buffers internally @@ -301,10 +338,26 @@ function fun($filter, $parameters = null) */ function remove($filter) { - if (@\stream_filter_remove($filter) === false) { + $errstr = ''; + \set_error_handler(function ($_, $error) use (&$errstr) { + // Match errstr from PHP's warning message. + // stream_filter_remove() expects parameter 1 to be resource,... + $errstr = $error; + }); + + try { + $ret = \stream_filter_remove($filter); + } catch (\TypeError $e) { // @codeCoverageIgnoreStart + // Throws TypeError on PHP 8.0+ + \restore_error_handler(); + throw $e; + } // @codeCoverageIgnoreEnd + + \restore_error_handler(); + + if ($ret === false) { // PHP 8 throws above on type errors, older PHP and memory issues can throw here - $error = \error_get_last(); - throw new \RuntimeException('Unable to remove filter: ' . $error['message']); + throw new \RuntimeException('Unable to remove filter: ' . $errstr); } } diff --git a/tests/FilterTest.php b/tests/FilterTest.php index 2b2d01d..f418588 100644 --- a/tests/FilterTest.php +++ b/tests/FilterTest.php @@ -335,31 +335,118 @@ public function testAppendThrowsShouldTriggerEndButIgnoreExceptionDuringEnd() $this->assertContainsString('test', $errors[0]); } - public function testAppendInvalidStreamIsRuntimeError() + public function testAppendThrowsForInvalidEncodingAndUnsetsUsedErrorHandler() + { + $handler = set_error_handler(function () { }); + + restore_error_handler(); + + try { + StreamFilter\append(false, function () { }); + } catch (\TypeError $e) { + // handle Error to unset Error handler afterwards (PHP >= 8.0) + } catch (\RunTimeException $e) { + // handle Error to unset Error handler afterwards (PHP < 8.0) + } + + $checkHandler = set_error_handler(function () { }); + restore_error_handler(); + + $this->assertEquals($handler, $checkHandler); + } + + public function testAppendInvalidStreamIsRuntimeErrorWithoutCallingCustomErrorHandler() { if (PHP_VERSION >= 8) $this->markTestSkipped('Not supported on PHP 8+ (PHP 8 throws TypeError automatically)'); $this->setExpectedException('RuntimeException'); if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (does not reject invalid stream)'); + + $error = null; + set_error_handler(function ($_, $errstr) use (&$error) { + $error = $errstr; + }); + StreamFilter\append(false, function () { }); + + restore_error_handler(); + $this->assertNull($error); } - public function testPrependInvalidStreamIsRuntimeError() + public function testPrependThrowsForInvalidEncodingAndUnsetsUsedErrorHandler() + { + $handler = set_error_handler(function () { }); + + restore_error_handler(); + + try { + StreamFilter\prepend(false, function () { }); + } catch (\TypeError $e) { + // handle Error to unset Error handler afterwards (PHP >= 8.0) + } catch (\RunTimeException $e) { + // handle Error to unset Error handler afterwards (PHP < 8.0) + } + + $checkHandler = set_error_handler(function () { }); + restore_error_handler(); + + $this->assertEquals($handler, $checkHandler); + } + + public function testPrependInvalidStreamIsRuntimeErrorWithoutCallingCustomErrorHandler() { if (PHP_VERSION >= 8) $this->markTestSkipped('Not supported on PHP 8+ (PHP 8 throws TypeError automatically)'); $this->setExpectedException('RuntimeException'); if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (does not reject invalid stream)'); + + $error = null; + set_error_handler(function ($_, $errstr) use (&$error) { + $error = $errstr; + }); + StreamFilter\prepend(false, function () { }); + + restore_error_handler(); + $this->assertNull($error); + } + + public function testRemoveThrowsForInvalidEncodingAndUnsetsUsedErrorHandler() + { + $handler = set_error_handler(function () { }); + + restore_error_handler(); + + try { + StreamFilter\remove(false); + } catch (\TypeError $e) { + // handle Error to unset Error handler afterwards (PHP >= 8.0) + } catch (\RunTimeException $e) { + // handle Error to unset Error handler afterwards (PHP < 8.0) + } + + $checkHandler = set_error_handler(function () { }); + restore_error_handler(); + + $this->assertEquals($handler, $checkHandler); } - public function testRemoveInvalidFilterIsRuntimeError() + public function testRemoveInvalidFilterIsRuntimeErrorWithoutCallingCustomErrorHandler() { if (PHP_VERSION >= 8) $this->markTestSkipped('Not supported on PHP 8+ (PHP 8 throws TypeError automatically)'); $this->setExpectedException('RuntimeException', 'Unable to remove filter: stream_filter_remove() expects parameter 1 to be resource, '); if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (does not reject invalid filters)'); + + $error = null; + set_error_handler(function ($_, $errstr) use (&$error) { + $error = $errstr; + }); + StreamFilter\remove(false); + + restore_error_handler(); + $this->assertNull($error); } /** diff --git a/tests/FunTest.php b/tests/FunTest.php index 5e03cbd..00a5e69 100644 --- a/tests/FunTest.php +++ b/tests/FunTest.php @@ -35,10 +35,19 @@ public function testFunWriteAfterCloseRot13() $rot('test'); } - public function testFunInvalid() + public function testFunInvalidWithoutCallingCustomErrorHandler() { $this->setExpectedException('RuntimeException'); + + $error = null; + set_error_handler(function ($_, $errstr) use (&$error) { + $error = $errstr; + }); + Filter\fun('unknown'); + + restore_error_handler(); + $this->assertNull($error); } public function testFunInBase64()