intermediate
Step 8 of 16
Error Handling and Exceptions
PHP Programming
Error Handling and Exceptions
Effective error handling is what separates professional PHP applications from fragile scripts. PHP has evolved from simple error reporting with warnings and notices to a full exception-based error handling system. Modern PHP 8.x throws exceptions for many situations that previously produced only warnings, making code behavior more predictable. Understanding how to catch, handle, and create custom exceptions is essential for building reliable applications that degrade gracefully when things go wrong.
Try/Catch/Finally
<?php
declare(strict_types=1);
try {
$data = json_decode('invalid json', true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
echo "JSON error: " . $e->getMessage() . "
";
}
// Multiple catch blocks
function processFile(string $path): string {
try {
if (!file_exists($path)) {
throw new RuntimeException("File not found: $path");
}
$content = file_get_contents($path);
if ($content === false) {
throw new RuntimeException("Could not read file: $path");
}
return $content;
} catch (RuntimeException $e) {
error_log($e->getMessage());
return "";
} finally {
echo "File operation completed
";
}
}
// Catching multiple exception types (PHP 8.0+)
try {
riskyOperation();
} catch (InvalidArgumentException | RangeException $e) {
echo "Input error: " . $e->getMessage();
} catch (RuntimeException $e) {
echo "Runtime error: " . $e->getMessage();
} catch (Throwable $e) {
echo "Unexpected error: " . $e->getMessage();
}
?>
Custom Exception Classes
<?php
class AppException extends RuntimeException {
public function __construct(
string $message,
private readonly string $errorCode = 'GENERAL_ERROR',
int $httpStatus = 500,
?Throwable $previous = null
) {
parent::__construct($message, $httpStatus, $previous);
}
public function getErrorCode(): string {
return $this->errorCode;
}
}
class ValidationException extends AppException {
private array $errors = [];
public function __construct(array $errors) {
$this->errors = $errors;
parent::__construct(
'Validation failed',
'VALIDATION_ERROR',
422
);
}
public function getErrors(): array {
return $this->errors;
}
}
class NotFoundException extends AppException {
public function __construct(string $resource, string|int $id) {
parent::__construct(
"$resource with ID $id not found",
'NOT_FOUND',
404
);
}
}
// Usage
function findUser(int $id): array {
$user = null; // Simulate DB lookup
if (!$user) {
throw new NotFoundException('User', $id);
}
return $user;
}
try {
$user = findUser(999);
} catch (NotFoundException $e) {
http_response_code($e->getCode());
echo json_encode([
'error' => $e->getErrorCode(),
'message' => $e->getMessage()
]);
}
function createUser(array $data): array {
$errors = [];
if (empty($data['name'])) $errors['name'] = 'Name is required';
if (empty($data['email'])) $errors['email'] = 'Email is required';
if (!empty($errors)) throw new ValidationException($errors);
return $data;
}
try {
createUser([]);
} catch (ValidationException $e) {
print_r($e->getErrors());
// ['name' => 'Name is required', 'email' => 'Email is required']
}
?>
Error Handling Configuration
<?php
// Development: show all errors
ini_set('display_errors', '1');
error_reporting(E_ALL);
// Production: log errors, don't display
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/php-errors.log');
// Global exception handler
set_exception_handler(function (Throwable $e) {
error_log(sprintf(
"[%s] %s in %s:%d
Stack trace:
%s",
get_class($e),
$e->getMessage(),
$e->getFile(),
$e->getLine(),
$e->getTraceAsString()
));
http_response_code(500);
echo json_encode(['error' => 'Internal server error']);
});
// Global error handler (converts errors to exceptions)
set_error_handler(function (int $severity, string $message, string $file, int $line) {
throw new ErrorException($message, 0, $severity, $file, $line);
});
?>
Pro tip: Always set a global exception handler withset_exception_handler()and convert PHP errors to exceptions withset_error_handler(). This ensures every error in your application is handled consistently. In production, log errors with full stack traces but show users only a generic error message to prevent exposing internal details.
Key Takeaways
- Use try/catch/finally for structured error handling; catch specific exception types before general ones.
- Create custom exception classes for domain-specific errors with error codes and HTTP status codes.
- Use
set_exception_handler()for a global fallback andset_error_handler()to convert errors to exceptions. - In production, log errors with details but display only generic messages to users.
- PHP 8.0+ catches multiple exception types in one block:
catch (TypeA | TypeB $e).