Contents
- 1 PHP error handling explained
- 1.1 Why error handling matters more than you think
- 1.2 The basics: Notices, warnings, and fatal errors
- 1.3 Simple fixes: Die and basic checks
- 1.4 Custom error handlers: Take control
- 1.5 Exceptions: The modern way since PHP 7
- 1.6 Advanced tricks: Logging, backtraces, and prod readiness
- 1.7 Error vs exception handlers
- 1.8 Real-world example: File processor with full handling
- 1.9 Common pitfalls and how to dodge them
- 1.10 Best practices for PHP teams
PHP error handling explained
Fellow developers, picture this: it's 2 AM, your coffee's gone cold, and that one sneaky bug just crashed your production app. Users see a blank screen. Your heart sinks. We've all been there. Error handling in PHP isn't just code—it's the difference between chaos and control, between looking like a rookie and owning the night.
PHP's error handling has evolved a lot. From crude die() calls to modern exceptions that let you breathe. Today, we're diving deep. I'll share what works, what fails, and the real-world tricks that save your sanity. Because good code doesn't just run—it survives.
Why error handling matters more than you think
Errors happen. Files vanish. Databases hiccup. APIs ghost you. Without solid handling, your script spews ugly messages or dies silently. Worse, in production, it hides problems from you while users rage-quit.
Think about security too. Leaky errors reveal paths, versions, database names. Hackers love that. Proper handling logs smartly, shows nothing to users, and keeps your app humming.
I've lost count of deadlines saved by a quick custom handler. It turns "oh no" into "handled it."
The basics: Notices, warnings, and fatal errors
PHP throws different error levels. Know them like your keyboard shortcuts.
- Notices: Minor stuff, like undefined variables. Annoying but rarely fatal.
- Warnings: Serious but recoverable, like missing files.
- Fatal errors: Game over. Script stops. Pre-PHP 7, untouchable. Now? Exceptions.
Use error_reporting(E_ALL) to see everything during dev. In production? Log, don't display.
Quick check: ini_set('display_errors', 1); error_reporting(E_ALL); at script top. Boom, transparency.
Simple fixes: Die and basic checks
Start here if you're green. The die() with file_exists() combo from old-school tutorials.
if (file_exists("data.txt")) {
$file = fopen("data.txt", "r");
} else {
die("Error: File missing. Check your paths.");
}
Clean. Stops the script gracefully. Better than crashing on fopen(). But it's blunt. No logging, no recovery. Fine for scripts, weak for apps.
Ever stared at a "file not found" at midnight? This catches it early.
Custom error handlers: Take control
This is where PHP shines. set_error_handler() lets you own errors.
Craft a function like this:
function myErrorHandler($errno, $errstr, $errfile, $errline) {
error_log("Error [$errno] in $errfile line $errline: $errstr");
echo "Something went wrong. We're on it.";
// Don't die unless fatal
}
set_error_handler("myErrorHandler");
Now, undefined vars or bad divides? Yours to tame. Parameters give level, message, file, line, even context array.
Test it: Trigger with $test = 2; if($test > 1) trigger_error("Value too high", E_USER_WARNING);
Your handler fires. Script continues if you return true. Magic.
Pro tip: In prod, pipe to error_log() or tools like Sentry. No user-facing spew.
Exceptions: The modern way since PHP 7
Forget scattered die(). Exceptions are structured. Throw, catch, recover.
try {
$file = fopen("data.txt", "r");
if (!$file) {
throw new Exception("Can't open file.");
}
echo fread($file, filesize("data.txt"));
} catch (Exception $e) {
echo "Handled: " . $e->getMessage();
} finally {
if ($file) fclose($file);
}
Clean flow. try wraps risky code. catch grabs exceptions. finally cleans up always.
PHP 7+ turns many fatals into Error exceptions. Catch Throwable for all:
catch (Throwable $e) {
error_log($e->getMessage() . " at " . $e->getFile() . ":" . $e->getLine());
}
Propagation? Let it bubble up. Upper layers decide: log, retry, bail.
I've refactored legacy code this way. Suddenly, bugs became features—handled, logged, fixed fast.
Advanced tricks: Logging, backtraces, and prod readiness
Handlers alone? Not enough. Stack traces reveal the crime scene.
Use debug_print_backtrace() in your handler:
function advancedHandler($errno, $errstr, $errfile, $errline) {
$trace = debug_backtrace();
error_log("Error: $errstr in $errfile:$errline\n" . print_r($trace, true));
}
Prod setup: ini_set('log_errors', 1); ini_set('error_log', '/var/log/php_errors.log'); display_errors Off.
Never @supress errors. That @fopen()? Hides bombs. Relic from PHP's wild days.
Error vs exception handlers
Two beasts:
set_error_handler()for warnings/notices.set_exception_handler()for uncaught throws.
Combo them:
set_error_handler(function($errno, $errstr, $errfile, $errline) {
if ($errno === E_WARNING) trigger_error($errstr, E_USER_ERROR); // Escalate
return false;
});
set_exception_handler(function(Throwable $e) {
error_log("Uncaught: " . $e->getMessage());
});
PHP 8.x? Even more errors as exceptions. Future-proof.
Real-world example: File processor with full handling
Let's build something useful. A CSV reader that laughs at failures.
function processCsv($filename) {
set_error_handler(function($errno, $errstr) {
throw new ErrorException($errstr, 0, $errno);
});
try {
if (!file_exists($filename)) {
throw new InvalidArgumentException("No such file: $filename");
}
$handle = fopen($filename, 'r');
if (!$handle) throw new RuntimeException("Failed to open.");
while (($data = fgetcsv($handle)) !== false) {
// Process row
yield $data;
}
} catch (Throwable $e) {
error_log("CSV fail: " . $e->getTraceAsString());
return []; // Graceful empty
} finally {
if (isset($handle)) fclose($handle);
restore_error_handler();
}
}
// Usage
foreach (processCsv('users.csv') as $row) {
echo implode(', ', $row) . "\n";
}
See? Yields rows or empty array. Logs everything. No crashes.
I used this in a data migration. Swallowed bad files, kept going. Client never knew.
Common pitfalls and how to dodge them
- Forgetting context: Always grab
$error_contextfor vars. - Prod displays:
display_errors=Off. Always. - Memory leaks: Handlers run fast. No heavy DB calls.
- Recursion: Handlers can error. Check
error_reporting(). - Legacy code: Wrap old funcs in try/catch.
Tools? Sentry, Rollbar. They tag errors with user, URL, breadcrumbs. Game-changer.
Best practices for PHP teams
- Central handler in bootstrap.
- Specific exceptions:
ValidationException,DatabaseGoneException. - Tests:
expectException(). - Logs: Rotate, alert on fatals.
Questions for you: When's the last time an unhandled error bit you? How do you log now?
Error handling feels tedious until it saves you. Then it's quiet heroism.
Master this, and your code whispers confidence. Next bug? Just another story.