set_error_handler()
PHP is an open-source scripting language that is widely used for web development. One of the essential aspects of any programming language is error handling,
PHP's default behavior on a non-fatal error is to print a message to the output (or log it) and keep running. That's rarely what you want in production: the raw message can leak file paths to visitors, and you have no central place to log, format, or convert errors. The set_error_handler() function fixes this by routing PHP's built-in errors through a function you control.
This chapter explains what set_error_handler() does, the exact callback signature PHP expects, which errors it can and cannot intercept, and how to use it for clean logging.
What set_error_handler() does
set_error_handler() registers a callback that PHP invokes instead of its built-in error handler whenever a matching (non-fatal) error is raised. Your callback decides what happens next — log it, display a friendly message, throw an exception, or silently ignore it.
It does not stop errors from being raised; it only changes how they are handled. PHP still respects error_reporting when deciding whether to call your handler at all.
Syntax
set_error_handler(
callable|null $callback,
int $error_levels = E_ALL
): callable|false| Parameter | Description |
|---|---|
$callback | The function PHP calls when an error fires. Pass null to restore PHP's built-in handler. |
$error_levels | A bitmask of the error levels your handler should receive (e.g. E_WARNING | E_NOTICE). Defaults to E_ALL. |
Return value: the previously registered handler (a callable), or null if there was none. It returns false only when the callback is invalid.
The callback signature
PHP calls your handler with up to five arguments. The first four are the ones you'll use most:
function handler(
int $errno, // the error level constant, e.g. E_WARNING
string $errstr, // the error message
string $errfile = "", // file where the error occurred
int $errline = 0 // line number
): bool {
// ...
}If your handler returns false, PHP continues with its normal internal error handling afterward. Returning true (or nothing) tells PHP the error has been fully handled and suppresses the default behavior.
Which errors it can catch
set_error_handler() intercepts the user-catchable error levels — warnings, notices, deprecations, and the E_USER_* family raised by trigger_error():
| Catchable | Not catchable (fatal) |
|---|---|
E_WARNING, E_NOTICE, E_DEPRECATED | E_ERROR |
E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE | E_PARSE |
E_RECOVERABLE_ERROR, E_STRICT | E_CORE_ERROR, E_COMPILE_ERROR |
Note: Fatal errors such as
E_ERRORandE_PARSEbypass your handler and terminate the script. To capture those, combine your handler withset_exception_handler()and a shutdown function. Uncaught exceptions are also not routed here — they have their own handler.
A basic handler
Define a function with the expected signature and register it. Here a trigger_error() call simulates a warning so you can see the handler run:
<?php
function customErrorHandler($errno, $errstr, $errfile, $errline)
{
echo "Handled error [$errno]: $errstr in $errfile on line $errline\n";
return true; // we consider the error fully handled
}
set_error_handler("customErrorHandler");
trigger_error("This is a test warning", E_USER_WARNING);
// Restore PHP's default error handling
restore_error_handler();Output:
Handled error [512]: This is a test warning in ... on line 11512 is the numeric value of E_USER_WARNING. After restore_error_handler(), errors go back to PHP's default behavior.
Converting errors to exceptions
A common production pattern is to turn warnings and notices into exceptions so they flow through your normal try/catch logic. This lets you handle a failed file_get_contents() (which only emits a warning) with try/catch:
<?php
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
// Respect the @ operator and error_reporting settings.
if (!(error_reporting() & $errno)) {
return false;
}
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});
try {
$data = file_get_contents("/no/such/file");
} catch (ErrorException $e) {
echo "Caught: " . $e->getMessage() . "\n";
}Output:
Caught: file_get_contents(/no/such/file): Failed to open stream: No such file or directoryLogging instead of displaying
Mapping the level constant to a readable label keeps logs scannable. Use error_log() to write to your configured log destination:
<?php
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
$levels = [
E_WARNING => "WARNING",
E_NOTICE => "NOTICE",
E_DEPRECATED => "DEPRECATED",
E_USER_WARNING => "USER_WARNING",
];
$label = $levels[$errno] ?? "UNKNOWN($errno)";
$line = "[$label] $errstr in $errfile:$errline";
error_log($line); // goes to your log, not the page
echo $line . "\n"; // shown here only to demonstrate the output
return true;
});
trigger_error("Disk space low", E_USER_WARNING);Output:
[USER_WARNING] Disk space low in ... :15Gotchas
- The
@operator. When an error is silenced with@,error_reporting()returns0inside your handler. Checkerror_reporting() & $errnoif you want to honor@, as shown above. - Fatal errors still escape. No matter the second argument,
E_ERRORand parse errors are never delivered to your handler. - It's global. A registered handler affects the whole request. Libraries that set their own handler can clobber yours, so restore handlers when you're done.
- Don't
echoin production. Displaying raw messages leaks paths and aids attackers — log them and show a generic page instead.
Related functions
restore_error_handler()— revert to the previous handler.trigger_error()— raise your ownE_USER_*errors.error_reporting()— control which levels are active.set_exception_handler()— catch uncaught exceptions.
Conclusion
set_error_handler() gives you one place to intercept PHP's non-fatal errors and apply your own logic — log them, convert them to exceptions, or hide them from users. Pair it with an exception handler and proper logging for production-grade error handling. Remember its one hard limit: fatal and parse errors bypass it entirely.