Error Handling
Errors in PHP In simple terms: When something goes wrong in a PHP script, PHP reports it — sometimes as a mild warning while the script continues, sometimes as a fatal error that stops everything. Err
Errors in PHP
In simple terms: When something goes wrong in a PHP script, PHP reports it — sometimes as a mild warning while the script continues, sometimes as a fatal error that stops everything. Error handling means deciding how these problems are reported, recorded, and shown (or hidden) to users, and writing code that deals with failures gracefully.
Technically: PHP has two error mechanisms:
| Mechanism | Examples | How It Behaves |
|---|---|---|
| Traditional errors (diagnostics) | Warnings, notices, deprecations | Reported according to error_reporting; the script usually continues |
Throwables (exceptions and Error) | TypeError, ValueError, DivisionByZeroError, your own exceptions | Can be caught with try/catch; uncaught ones stop the script |
Since PHP 7 and especially PHP 8, many former fatal errors and warnings were converted into Error exceptions, which can be caught. Exceptions are covered in PHP Exceptions; this lesson covers the traditional errors and the overall strategy.
Error Levels
| Constant | Severity | Example | Script Continues? |
|---|---|---|---|
E_ERROR | Fatal runtime error | Memory limit exhausted | No |
E_PARSE | Syntax error | Missing semicolon | No (the file cannot run) |
E_WARNING | Warning | File not found in file_get_contents() | Yes |
E_NOTICE | Notice | Minor issues (few remain in PHP 8) | Yes |
E_DEPRECATED | Deprecation | Using a feature that will be removed | Yes |
E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE, E_USER_DEPRECATED | Triggered by your code with trigger_error() | Custom | Depends |
E_ALL | All of the above | — | — |
Version notes:
E_STRICTis deprecated as of PHP 8.4 (its messages were merged into other levels long ago). PassingE_USER_ERRORtotrigger_error()is deprecated in PHP 8.4 — throw an exception instead.
Example: Warnings and Deprecations Do Not Stop the Script
<?php
$prices = [10, 20];
$price = $prices[5];
$total = $undefined;
$length = strlen(null);
echo "Still running\n";
Output
PHP Warning: Undefined array key 5 in /path/script.php on line 4
PHP Warning: Undefined variable $undefined in /path/script.php on line 5
PHP Deprecated: strlen(): Passing null to parameter #1 ($string) of type string is deprecated in /path/script.php on line 6
Still running
The script continues, but $price and $total are null — later code may produce wrong results. Warnings are signs of bugs and should be fixed, not ignored.
Configuring Error Reporting
Four php.ini settings control what happens with errors:
| Setting | Development | Production | Meaning |
|---|---|---|---|
error_reporting | E_ALL | E_ALL | Which errors are reported at all |
display_errors | On | Off | Whether errors are shown in the output (browser) |
log_errors | On | On | Whether errors are written to the log |
error_log | (optional) | A log file path, e.g. /var/log/php/app-error.log | Where errors are logged (default: the web server's error log or stderr) |
Why Different Settings?
- In development, you want to see every problem immediately, in the browser.
- In production, error messages can reveal file paths, database names, queries, and code structure to attackers. Errors must be logged for developers but never displayed to visitors (see Error Reporting in Production).
error_reporting should be E_ALL in both environments: hiding warnings in production does not fix them, it only hides the evidence.
Setting Them at Runtime
<?php
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/../storage/logs/php-error.log');
Settings in php.ini or the server configuration are preferable, because they also apply to errors that occur before your script starts (such as syntax errors in included files).
Preventing Errors Instead of Suppressing Them
Most warnings can be avoided by checking conditions first:
| Instead of | Write |
|---|---|
$value = $array['key']; | $value = $array['key'] ?? null; |
file_get_contents($path) on a file that may be missing | is_readable($path) ? file_get_contents($path) : null |
strlen($maybeNull) | strlen($maybeNull ?? '') |
$count = count($maybeArray); | $count = is_array($maybeArray) ? count($maybeArray) : 0; |
The @ Operator
The error-control operator @ suppresses the display of errors from one expression:
<?php
$content = @file_get_contents('/no/such/file.txt');
var_dump($content);
echo error_get_last()['message'] . "\n";
Output
bool(false)
file_get_contents(/no/such/file.txt): Failed to open stream: No such file or directory
The error still happened — error_get_last() returns it — but it was not displayed or logged. Using @ routinely hides real problems and makes debugging much harder. Prefer checking conditions first, or converting errors into exceptions (see Custom Error Handler). In PHP 8, @ no longer suppresses fatal errors.
Errors That Became Exceptions in PHP 8
Many functions now throw TypeError or ValueError instead of returning false with a warning:
<?php
try {
array_combine(['a', 'b'], [1]);
} catch (ValueError $e) {
echo get_class($e) . ": " . $e->getMessage() . "\n";
}
try {
str_repeat('-', -5);
} catch (ValueError $e) {
echo get_class($e) . ": " . $e->getMessage() . "\n";
}
Output
ValueError: array_combine(): Argument #1 ($keys) and argument #2 ($values) must have the same number of elements
ValueError: str_repeat(): Argument #2 ($times) must be greater than or equal to 0
These indicate programming mistakes (invalid arguments). They are usually not caught locally; instead, they are fixed in the code. A global handler ensures that, if one slips through, it is logged and the user sees a proper error page.
Handling Uncaught Exceptions Globally
set_exception_handler() registers a function that runs for any exception or Error that is not caught anywhere:
<?php
set_exception_handler(function (Throwable $e): void {
error_log(sprintf(
'[%s] %s in %s:%d',
get_class($e),
$e->getMessage(),
$e->getFile(),
$e->getLine()
));
http_response_code(500);
echo "Sorry, something went wrong. Please try again later.\n";
});
function loadDashboard(): void
{
throw new RuntimeException('Database unavailable');
}
loadDashboard();
echo "This line is not reached\n";
Output (what the visitor sees)
Sorry, something went wrong. Please try again later.
What Happens
- The exception is not caught, so PHP calls the registered handler.
- The handler logs the technical details for developers.
- It sends HTTP status 500 and a friendly message — no file paths or internal messages are shown.
- After the handler finishes, the script ends.
In a real application, the handler would render a proper error page (and show full details only in development).
Fatal Errors and Shutdown Functions
Some errors — such as exhausting the memory limit or exceeding the maximum execution time — cannot be caught. A shutdown function runs at the very end of every request and can detect them:
<?php
register_shutdown_function(function (): void {
$error = error_get_last();
if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
error_log("Fatal error: {$error['message']} in {$error['file']}:{$error['line']}");
if (!headers_sent()) {
http_response_code(500);
}
echo "Sorry, something went wrong.";
}
});
This guarantees that even fatal errors are logged and answered with a proper status code. A complete setup combining error handlers, exception handlers, and shutdown functions is shown in Custom Error Handler.
An Error-Handling Strategy
| Situation | Approach |
|---|---|
| Expected problems (missing optional file, invalid user input) | Check conditions; return null or validation errors |
| Failures your code cannot continue from (database down, payment API error) | Throw exceptions; catch where you can recover or inform the user |
Programming mistakes (TypeError, ValueError, warnings) | Fix them; log them if they occur in production |
| Anything uncaught | Global exception handler: log, show a safe error page |
| Fatal errors | Shutdown function: log, send status 500 |
Common Mistakes
Displaying Errors in Production
Effect: Attackers learn file paths, queries, and configuration details.
Fix: display_errors = Off, log_errors = On in production.
Lowering error_reporting to Hide Warnings
Effect: Bugs remain and become harder to find.
Fix: Keep E_ALL and fix the underlying problems.
Using @ Everywhere
Effect: Failures happen silently.
Fix: Check conditions first or convert errors to exceptions.
Ignoring Deprecation Notices
Effect: Code breaks when upgrading to the next PHP version.
Fix: Log deprecations and fix them regularly (see Migrating to PHP 8).
No Global Handler
Effect: Users see raw error messages or blank pages, and problems are not logged.
Fix: Register exception and shutdown handlers at application startup.
Best Practices
- Use
error_reporting = E_ALLeverywhere; display errors only in development. - Always log errors in production, and review the logs.
- Prevent warnings by checking conditions instead of suppressing them.
- Use exceptions for failures and install global handlers for anything uncaught.
- Treat warnings and deprecations as bugs to be fixed.
Official reference: https://www.php.net/manual/en/language.errors.php