Skip to content

Commit 8f64659

Browse files
authored
Merge pull request #47 from SimonFrings/error-handler
Improve error reporting when custom error handler is used
2 parents b29e479 + 8e6f50a commit 8f64659

File tree

3 files changed

+166
-17
lines changed

3 files changed

+166
-17
lines changed

src/functions.php

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,27 @@
104104
*/
105105
function append($stream, $callback, $read_write = STREAM_FILTER_ALL)
106106
{
107-
$ret = @\stream_filter_append($stream, register(), $read_write, $callback);
107+
$errstr = '';
108+
\set_error_handler(function ($_, $error) use (&$errstr) {
109+
// Match errstr from PHP's warning message.
110+
// stream_filter_append() expects parameter 1 to be resource,...
111+
$errstr = $error; // @codeCoverageIgnore
112+
});
113+
114+
try {
115+
$ret = \stream_filter_append($stream, register(), $read_write, $callback);
116+
} catch (\TypeError $e) { // @codeCoverageIgnoreStart
117+
// Throws TypeError on PHP 8.0+
118+
\restore_error_handler();
119+
throw $e;
120+
} // @codeCoverageIgnoreEnd
121+
122+
\restore_error_handler();
108123

109124
// PHP 8 throws above on type errors, older PHP and memory issues can throw here
110125
// @codeCoverageIgnoreStart
111126
if ($ret === false) {
112-
$error = \error_get_last() + array('message' => '');
113-
throw new \RuntimeException('Unable to append filter: ' . $error['message']);
127+
throw new \RuntimeException('Unable to append filter: ' . $errstr);
114128
}
115129
// @codeCoverageIgnoreEnd
116130

@@ -147,13 +161,27 @@ function append($stream, $callback, $read_write = STREAM_FILTER_ALL)
147161
*/
148162
function prepend($stream, $callback, $read_write = STREAM_FILTER_ALL)
149163
{
150-
$ret = @\stream_filter_prepend($stream, register(), $read_write, $callback);
164+
$errstr = '';
165+
\set_error_handler(function ($_, $error) use (&$errstr) {
166+
// Match errstr from PHP's warning message.
167+
// stream_filter_prepend() expects parameter 1 to be resource,...
168+
$errstr = $error; // @codeCoverageIgnore
169+
});
170+
171+
try {
172+
$ret = \stream_filter_prepend($stream, register(), $read_write, $callback);
173+
} catch (\TypeError $e) { // @codeCoverageIgnoreStart
174+
// Throws TypeError on PHP 8.0+
175+
\restore_error_handler();
176+
throw $e;
177+
} // @codeCoverageIgnoreEnd
178+
179+
\restore_error_handler();
151180

152181
// PHP 8 throws above on type errors, older PHP and memory issues can throw here
153182
// @codeCoverageIgnoreStart
154183
if ($ret === false) {
155-
$error = \error_get_last() + array('message' => '');
156-
throw new \RuntimeException('Unable to prepend filter: ' . $error['message']);
184+
throw new \RuntimeException('Unable to prepend filter: ' . $errstr);
157185
}
158186
// @codeCoverageIgnoreEnd
159187

@@ -242,16 +270,25 @@ function prepend($stream, $callback, $read_write = STREAM_FILTER_ALL)
242270
function fun($filter, $parameters = null)
243271
{
244272
$fp = \fopen('php://memory', 'w');
273+
274+
$errstr = '';
275+
\set_error_handler(function ($_, $error) use (&$errstr) {
276+
// Match errstr from PHP's warning message.
277+
// stream_filter_append() expects parameter 1 to be resource,...
278+
$errstr = $error;
279+
});
280+
245281
if (\func_num_args() === 1) {
246-
$filter = @\stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE);
282+
$filter = \stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE);
247283
} else {
248-
$filter = @\stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE, $parameters);
284+
$filter = \stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE, $parameters);
249285
}
250286

287+
\restore_error_handler();
288+
251289
if ($filter === false) {
252290
\fclose($fp);
253-
$error = \error_get_last() + array('message' => '');
254-
throw new \RuntimeException('Unable to access built-in filter: ' . $error['message']);
291+
throw new \RuntimeException('Unable to access built-in filter: ' . $errstr);
255292
}
256293

257294
// append filter function which buffers internally
@@ -301,10 +338,26 @@ function fun($filter, $parameters = null)
301338
*/
302339
function remove($filter)
303340
{
304-
if (@\stream_filter_remove($filter) === false) {
341+
$errstr = '';
342+
\set_error_handler(function ($_, $error) use (&$errstr) {
343+
// Match errstr from PHP's warning message.
344+
// stream_filter_remove() expects parameter 1 to be resource,...
345+
$errstr = $error;
346+
});
347+
348+
try {
349+
$ret = \stream_filter_remove($filter);
350+
} catch (\TypeError $e) { // @codeCoverageIgnoreStart
351+
// Throws TypeError on PHP 8.0+
352+
\restore_error_handler();
353+
throw $e;
354+
} // @codeCoverageIgnoreEnd
355+
356+
\restore_error_handler();
357+
358+
if ($ret === false) {
305359
// PHP 8 throws above on type errors, older PHP and memory issues can throw here
306-
$error = \error_get_last();
307-
throw new \RuntimeException('Unable to remove filter: ' . $error['message']);
360+
throw new \RuntimeException('Unable to remove filter: ' . $errstr);
308361
}
309362
}
310363

tests/FilterTest.php

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -335,31 +335,118 @@ public function testAppendThrowsShouldTriggerEndButIgnoreExceptionDuringEnd()
335335
$this->assertContainsString('test', $errors[0]);
336336
}
337337

338-
public function testAppendInvalidStreamIsRuntimeError()
338+
public function testAppendThrowsForInvalidEncodingAndUnsetsUsedErrorHandler()
339+
{
340+
$handler = set_error_handler(function () { });
341+
342+
restore_error_handler();
343+
344+
try {
345+
StreamFilter\append(false, function () { });
346+
} catch (\TypeError $e) {
347+
// handle Error to unset Error handler afterwards (PHP >= 8.0)
348+
} catch (\RunTimeException $e) {
349+
// handle Error to unset Error handler afterwards (PHP < 8.0)
350+
}
351+
352+
$checkHandler = set_error_handler(function () { });
353+
restore_error_handler();
354+
355+
$this->assertEquals($handler, $checkHandler);
356+
}
357+
358+
public function testAppendInvalidStreamIsRuntimeErrorWithoutCallingCustomErrorHandler()
339359
{
340360
if (PHP_VERSION >= 8) $this->markTestSkipped('Not supported on PHP 8+ (PHP 8 throws TypeError automatically)');
341361

342362
$this->setExpectedException('RuntimeException');
343363
if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (does not reject invalid stream)');
364+
365+
$error = null;
366+
set_error_handler(function ($_, $errstr) use (&$error) {
367+
$error = $errstr;
368+
});
369+
344370
StreamFilter\append(false, function () { });
371+
372+
restore_error_handler();
373+
$this->assertNull($error);
345374
}
346375

347-
public function testPrependInvalidStreamIsRuntimeError()
376+
public function testPrependThrowsForInvalidEncodingAndUnsetsUsedErrorHandler()
377+
{
378+
$handler = set_error_handler(function () { });
379+
380+
restore_error_handler();
381+
382+
try {
383+
StreamFilter\prepend(false, function () { });
384+
} catch (\TypeError $e) {
385+
// handle Error to unset Error handler afterwards (PHP >= 8.0)
386+
} catch (\RunTimeException $e) {
387+
// handle Error to unset Error handler afterwards (PHP < 8.0)
388+
}
389+
390+
$checkHandler = set_error_handler(function () { });
391+
restore_error_handler();
392+
393+
$this->assertEquals($handler, $checkHandler);
394+
}
395+
396+
public function testPrependInvalidStreamIsRuntimeErrorWithoutCallingCustomErrorHandler()
348397
{
349398
if (PHP_VERSION >= 8) $this->markTestSkipped('Not supported on PHP 8+ (PHP 8 throws TypeError automatically)');
350399

351400
$this->setExpectedException('RuntimeException');
352401
if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (does not reject invalid stream)');
402+
403+
$error = null;
404+
set_error_handler(function ($_, $errstr) use (&$error) {
405+
$error = $errstr;
406+
});
407+
353408
StreamFilter\prepend(false, function () { });
409+
410+
restore_error_handler();
411+
$this->assertNull($error);
412+
}
413+
414+
public function testRemoveThrowsForInvalidEncodingAndUnsetsUsedErrorHandler()
415+
{
416+
$handler = set_error_handler(function () { });
417+
418+
restore_error_handler();
419+
420+
try {
421+
StreamFilter\remove(false);
422+
} catch (\TypeError $e) {
423+
// handle Error to unset Error handler afterwards (PHP >= 8.0)
424+
} catch (\RunTimeException $e) {
425+
// handle Error to unset Error handler afterwards (PHP < 8.0)
426+
}
427+
428+
$checkHandler = set_error_handler(function () { });
429+
restore_error_handler();
430+
431+
$this->assertEquals($handler, $checkHandler);
354432
}
355433

356-
public function testRemoveInvalidFilterIsRuntimeError()
434+
public function testRemoveInvalidFilterIsRuntimeErrorWithoutCallingCustomErrorHandler()
357435
{
358436
if (PHP_VERSION >= 8) $this->markTestSkipped('Not supported on PHP 8+ (PHP 8 throws TypeError automatically)');
359437

360438
$this->setExpectedException('RuntimeException', 'Unable to remove filter: stream_filter_remove() expects parameter 1 to be resource, ');
361439
if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (does not reject invalid filters)');
440+
441+
$error = null;
442+
set_error_handler(function ($_, $errstr) use (&$error) {
443+
$error = $errstr;
444+
});
445+
362446
StreamFilter\remove(false);
447+
448+
restore_error_handler();
449+
$this->assertNull($error);
363450
}
364451

365452
/**

tests/FunTest.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,19 @@ public function testFunWriteAfterCloseRot13()
3535
$rot('test');
3636
}
3737

38-
public function testFunInvalid()
38+
public function testFunInvalidWithoutCallingCustomErrorHandler()
3939
{
4040
$this->setExpectedException('RuntimeException');
41+
42+
$error = null;
43+
set_error_handler(function ($_, $errstr) use (&$error) {
44+
$error = $errstr;
45+
});
46+
4147
Filter\fun('unknown');
48+
49+
restore_error_handler();
50+
$this->assertNull($error);
4251
}
4352

4453
public function testFunInBase64()

0 commit comments

Comments
 (0)