set_error_handler and error_reporting configuration
Concept
set_error_handler($callback) registers a custom function to handle PHP errors. When an error that matches the current error_reporting mask occurs, your handler is called instead of PHP's default handler. This enables converting errors to exceptions, structured logging, or custom error pages.
Handler signature: handler(int $errno, string $errstr, string $errfile, int $errline): bool. Return true to suppress PHP's default behavior. Return false (or nothing) to let the default handler also run. Throw an exception from the handler to convert it into an exception.
Errors your handler cannot intercept: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING. These are fatal/compilation-time errors handled before user code runs. Use register_shutdown_function to detect fatals.
Best practice — convert warnings to exceptions: Many PHP functions that fail emit warnings instead of throwing exceptions (older API style). Registering an error handler that throws \ErrorException for E_WARNING and higher converts these into catchable exceptions.
restore_error_handler(): Restores the previous handler (error handlers are stacked). Useful for temporary handler overrides.
Code Example
<?php
declare(strict_types=1);
// Convert warnings and above to ErrorException
set_error_handler(function(int $errno, string $errstr, string $errfile, int $errline): bool {
if (!(error_reporting() & $errno)) {
return false; // error is suppressed by error_reporting level
}
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
}, E_WARNING | E_NOTICE | E_DEPRECATED);
// Now warnings become catchable exceptions
try {
$arr = [1, 2, 3];
echo $arr[99]; // E_NOTICE: undefined array key — now throws ErrorException
} catch (\ErrorException $e) {
echo "Caught: " . $e->getMessage() . "\n";
}
// Production error handler — log without leaking details
function productionErrorHandler(int $errno, string $errstr, string $errfile, int $errline): bool
{
$message = sprintf('[PHP %d] %s in %s:%d', $errno, $errstr, $errfile, $errline);
error_log($message);
// Don't display to user in production — just log it
return true; // suppress default output
}
set_error_handler('productionErrorHandler');
// Temporarily change error handler
set_error_handler(fn() => true); // suppress all errors
someLeakyLegacyFunction();
restore_error_handler(); // restore previous handler
// Error handler stack demonstration
set_error_handler(fn($e, $m) => print("Handler 1: $m\n") || false);
set_error_handler(fn($e, $m) => print("Handler 2: $m\n") || true); // returns true
trigger_error("Test error", E_USER_NOTICE);
// Only "Handler 2" runs — it returned true (suppress default + prevent stack fallback)
// Actually: only the LAST registered handler runs (it's not a chain, it's a stack)
restore_error_handler();
trigger_error("Test 2", E_USER_NOTICE);
// "Handler 1" now runs