Custom Error Handlers (set_error_handler)
debt(d7/e3/b3/t7)
Closest to 'only careful code review or runtime testing' (d7). PHPStan (listed in detection_hints.tools) can flag the presence of set_error_handler() calls but cannot reliably detect that fatal errors are silently unhandled or that the handler returns the wrong value — those misuse patterns only surface during runtime testing or manual review.
Closest to 'simple parameterised fix' (e3). The quick_fix shows a one-liner handler registration, but correctly fixing the pattern requires also adding register_shutdown_function() + error_get_last(), restoring the previous handler in tests, and ensuring return true — a small but multi-step fix within one component rather than a single-line swap.
Closest to 'localised tax' (b3). The handler applies to web, cli, and queue-worker contexts (broad applies_to), but a correctly registered custom error handler is mostly self-contained in a bootstrap/init file. It imposes a persistent but manageable tax: future maintainers must know the handler exists and must restore it in tests, but it doesn't reshape the entire codebase.
Closest to 'serious trap' (t7). The misconception field explicitly states the canonical wrong belief: developers assume set_error_handler() catches all errors, but it silently misses E_ERROR and E_PARSE fatal errors. Combined with common mistakes (not returning true, not restoring handler in tests), this contradicts the intuitive expectation that a 'catch-all error handler' truly catches everything, making it a serious trap.
TL;DR
Explanation
PHP triggers errors via a global error handler. set_error_handler(callable $handler) replaces it for user-level errors (E_WARNING, E_NOTICE, E_USER_ERROR etc.) but cannot catch E_ERROR, E_PARSE, or E_CORE_ERROR — those still kill the script unless register_shutdown_function() is used. The callback receives ($errno, $errstr, $errfile, $errline). Always restore the previous handler with restore_error_handler() in tests. set_exception_handler() is the equivalent for uncaught exceptions.
Common Misconception
Why It Matters
Common Mistakes
- Forgetting to return true from the handler — PHP then executes the internal handler too.
- Not restoring the previous handler — breaks test isolation.
- Using set_error_handler() alone without register_shutdown_function() — misses fatal errors.
Code Examples
set_error_handler(function($errno, $errstr) {
// Silently ignores — errors disappear
});
set_error_handler(function(int $errno, string $errstr, string $errfile, int $errline): bool {
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
});
register_shutdown_function(function() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR])) {
// Log fatal error before script dies
error_log("Fatal: {$error['message']} in {$error['file']}:{$error['line']}");
}
});