PHP Error Handling — Exceptions, Error Levels, and Custom Error Handlers
In this tutorial, you will learn about PHP Error Handling. We cover key concepts, practical examples, and best practices to help you master this topic.
PHP error handling uses a combination of error levels for warnings and notices, exceptions for recoverable errors with try-catch blocks, and custom handlers for centralized error management across the application lifecycle.
Why It Matters
Errors happen. A file is missing, a database connection fails, or a user enters invalid data. How your application responds determines whether users see a white screen of death or a helpful error message. PHP's error handling system has evolved from simple error reporting to a robust exception model. Understanding errors vs exceptions, when to use each, and how to log and display them appropriately separates professional applications from hobby projects.
Real-World Use
Laravel's error handler converts PHP errors into exceptions and renders beautiful debug pages in development. Symfony's error handler logs errors and returns appropriate HTTP status codes. WordPress uses a custom error handler to suppress notices in production. DodaTech's production servers log all errors to a central system while returning generic error messages to users to avoid leaking sensitive information.
What You Will Learn
- PHP error levels: E_WARNING, E_NOTICE, E_ERROR, and others
- The error_reporting and display_errors configuration
- Try-catch-finally for Exception Handling
- Custom error handlers with set_error_handler
- Custom exception handlers with set_exception_handler
- Logging errors to files and external services
- Development vs production error configuration
Learning Path
flowchart LR A[JSON API] --> B[Error Handling
You are here] B --> C[Classes and OOP] C --> D[Inheritance and Traits] D --> E[Namespaces] style B fill:#f90,color:#fff
PHP Error Levels
PHP categorizes errors by severity:
<?php
// Fatal errors - script stops
// E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR
// Warnings - non-fatal, script continues
// E_WARNING, E_NOTICE, E_USER_WARNING, E_DEPRECATED
// Notices - minor issues
// E_NOTICE, E_STRICT
Common error levels:
| Constant | Value | Description |
|---|---|---|
| E_ERROR | 1 | Fatal runtime error, execution stops |
| E_WARNING | 2 | Non-fatal warning, execution continues |
| E_PARSE | 4 | Compile-time parse error |
| E_NOTICE | 8 | Notice of possible issue |
| E_DEPRECATED | 8192 | Warning about deprecated code |
| E_USER_ERROR | 256 | User-generated error with trigger_error |
| E_ALL | 32767 | All errors and warnings |
Error Reporting Configuration
Control which errors are reported and displayed:
<?php
// Development: show all errors
error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
// Production: log errors, do not display
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('display_startup_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/php_errors.log');
Triggering Errors
You can trigger errors programmatically:
<?php
function divide(int $a, int $b): float {
if ($b === 0) {
trigger_error('Division by zero', E_USER_WARNING);
return 0;
}
return $a / $b;
}
echo divide(10, 0); // Triggers E_USER_WARNING
Try-Catch Exceptions
PHP 5 introduced exceptions for object-oriented error handling:
<?php
function readConfig(string $file): array {
if (!file_exists($file)) {
throw new \RuntimeException("Config file not found: $file");
}
$data = parse_ini_file($file);
if ($data === false) {
throw new \RuntimeException("Failed to parse config: $file");
}
return $data;
}
try {
$config = readConfig('/etc/app/config.ini');
echo "Database host: " . $config['db_host'];
} catch (\RuntimeException $e) {
echo "Configuration error: " . $e->getMessage();
// Log the error
error_log($e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
}
Multiple Catch Blocks
<?php
try {
$result = processPayment($amount, $cardNumber);
} catch (\InvalidArgumentException $e) {
// Validation error - show to user
echo "Invalid input: " . $e->getMessage();
} catch (\PDOException $e) {
// Database error - log, do not show details
error_log("Database error: " . $e->getMessage());
echo "A system error occurred. Please try again later.";
} catch (\Throwable $e) {
// Any other error
error_log("Unexpected error: " . $e->getMessage());
echo "An unexpected error occurred.";
}
Finally Block
The finally block always executes, whether or not an exception was thrown:
<?php
function processFile(string $path): void {
$handle = fopen($path, 'r');
if (!$handle) {
throw new \RuntimeException("Cannot open file: $path");
}
try {
// Process file
while (($line = fgets($handle)) !== false) {
// Parse line
}
} finally {
fclose($handle); // Always close the handle
}
}
Custom Exception Classes
Create domain-specific exceptions for better error handling:
<?php
namespace App\Exception;
class DatabaseException extends \RuntimeException {}
class ValidationException extends \InvalidArgumentException {}
class AuthenticationException extends \RuntimeException {}
class NotFoundException extends \RuntimeException {}
// Usage
class UserService {
public function findById(int $id): array {
$user = $this->db->find('users', $id);
if (!$user) {
throw new NotFoundException("User with ID $id not found");
}
return $user;
}
public function register(array $data): array {
if (empty($data['email'])) {
throw new ValidationException('Email is required');
}
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
throw new ValidationException('Invalid email format');
}
// ...
}
}
Custom Error Handler
Convert PHP errors to exceptions for uniform handling:
<?php
set_error_handler(function (int $level, string $message, string $file, int $line): bool {
// Convert error to ErrorException
if (error_reporting() & $level) {
throw new \ErrorException($message, 0, $level, $file, $line);
}
return true; // Prevent PHP's internal error handler
});
// Now all PHP errors become exceptions
try {
echo $undefinedVariable; // Triggers E_NOTICE, now thrown as exception
} catch (\ErrorException $e) {
echo "Caught error: " . $e->getMessage();
}
Restoring the Error Handler
// Temporarily override
set_error_handler($customHandler);
// Do risky operation
restore_error_handler(); // Restore previous handler
Custom Exception Handler
Catch all uncaught exceptions at the application level:
<?php
set_exception_handler(function (\Throwable $e): never {
// Log the error
error_log(sprintf(
"Uncaught %s: %s in %s:%d\n%s",
get_class($e),
$e->getMessage(),
$e->getFile(),
$e->getLine(),
$e->getTraceAsString()
));
// Return appropriate response
if (PHP_SAPI === 'cli') {
echo "Fatal error: " . $e->getMessage() . "\n";
exit(1);
}
http_response_code(500);
header('Content-Type: application/json');
echo json_encode([
'error' => true,
'message' => 'An internal error occurred',
]);
exit;
});
Development vs Production Configuration
<?php
class ErrorConfig {
public static function configure(string $environment): void {
if ($environment === 'development') {
error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
ini_set('log_errors', '1');
} else {
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
ini_set('display_errors', '0');
ini_set('display_startup_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/app/php_errors.log');
}
}
}
// Usage at application entry point
ErrorConfig::configure($_ENV['APP_ENV'] ?? 'production');
Graceful Shutdown
Register a shutdown function for fatal errors:
<?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])) {
error_log(sprintf(
"Fatal: %s in %s:%d",
$error['message'],
$error['file'],
$error['line']
));
if (PHP_SAPI !== 'cli') {
http_response_code(500);
echo "A critical error occurred.";
}
}
});
Common Mistakes
1. Suppressing Errors with @
$result = @file_get_contents('/nonexistent/file'); // Bad practice
Use proper error handling instead of suppression. The @ operator hides all errors and makes debugging difficult.
2. Displaying Detailed Errors in Production
ini_set('display_errors', '1'); // Exposes file paths, stack traces
Set display_errors = Off in production. Log errors instead.
3. Catching Exception Too Broadly
try {
// code
} catch (\Exception $e) { // Too broad
Catch specific exception types. Use \Throwable only at the top-level handler.
4. Not Logging Errors
catch (\Exception $e) {
echo $e->getMessage(); // User sees message, but it is not logged
}
Always log errors for diagnosis. Display only generic messages to users.
5. Forgetting Finally for Resource Cleanup
If an exception occurs, file handles and database connections may leak. Use finally or destructors for cleanup.
Practice Questions
What is the difference between E_ERROR and E_WARNING? E_ERROR stops script execution. E_WARNING allows the script to continue. Both are logged if logging is enabled.
What does set_error_handler do? It overrides PHP's default error handler with a custom function. The custom handler receives error level, message, file, and line.
What is the \Throwable interface? It is the base interface for all errors and exceptions in PHP 7+. Both \Exception and \Error implement it.
How do you prevent sensitive information from leaking in production errors? Set display_errors=Off, log errors to a file, return a generic error page, and never include details in the response.
Challenge: Build a complete error handling system with a custom error handler that converts errors to exceptions, an exception handler for uncaught exceptions, a shutdown handler for fatal errors, and environment-aware configuration.
Mini Project: Error Handling Bootstrap
Create an application bootstrap with comprehensive error handling:
<?php
class Application {
private string $env;
public function __construct(string $env = 'production') {
$this->env = $env;
$this->configureErrorHandling();
}
private function configureErrorHandling(): void {
if ($this->env === 'development') {
error_reporting(E_ALL);
ini_set('display_errors', '1');
} else {
error_reporting(E_ALL & ~E_DEPRECATED);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/../var/log/error.log');
}
set_error_handler(function ($level, $message, $file, $line) {
if (error_reporting() & $level) {
throw new \ErrorException($message, 0, $level, $file, $line);
}
return true;
});
set_exception_handler(function (\Throwable $e) {
error_log(sprintf("[%s] %s in %s:%d", get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()));
if ($this->env === 'development') {
echo "<pre>" . $e . "</pre>";
} else {
http_response_code(500);
echo "An internal error occurred.";
}
exit;
});
register_shutdown_function(function () {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
error_log("FATAL: {$error['message']} in {$error['file']}:{$error['line']}");
if ($this->env !== 'development') {
http_response_code(500);
echo "A critical error occurred.";
}
}
});
}
}
$app = new Application($_ENV['APP_ENV'] ?? 'production');
FAQ
What is Next
Now that you understand error handling, proceed to Classes and OOP to learn object-oriented programming in PHP. Then read Inheritance and Traits to understand code reuse through inheritance.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro