Chapter 11: Errors and Exceptions
PHP distinguishes between errors and exceptions. Errors signal runtime problems in code or environment; exceptions represent controllable failures that you can catch and handle. This chapter explores error levels, the Throwable interface, structured exception handling, custom exception types, user error handlers, and practical logging strategies.
Error Levels and Reporting
PHP classifies errors by severity and behavior. Some issues are recoverable; others halt execution. You can configure reporting to suit development or production needs. Controlling error levels ensures useful diagnostics without leaking sensitive details.
Understanding PHP error types
Error levels include fatal errors, warnings, notices, and deprecations. Modern PHP represents all errors as Error exceptions that implement Throwable, but legacy code may still emit traditional messages.
| Level | Description |
E_ERROR | Fatal error that stops execution |
E_WARNING | Non fatal issue that continues execution |
E_NOTICE | Informational message about questionable code |
E_DEPRECATED | Use of a feature that will be removed |
E_PARSE | Compile time parse error |
Configuring error_reporting() and display settings
Use error_reporting() to control which levels PHP reports, and set display_errors and log_errors in php.ini or at runtime. During development show everything; in production hide output and log instead.
// Show all errors during development
error_reporting(E_ALL);
ini_set("display_errors", "1");
// Hide errors but log in production
error_reporting(E_ALL);
ini_set("display_errors", "0");
ini_set("log_errors", "1");
ini_set("error_log", "/var/log/php_errors.log");
Exceptions and Throwable
Exceptions are objects that represent failures during program execution. They implement the Throwable interface, which unifies Error and Exception under a single hierarchy. Throwing and catching exceptions lets you separate error handling from normal logic.
Throwing and catching basic exceptions
Use the throw keyword to signal an error and try...catch to intercept it. PHP unwinds the call stack until it finds a matching catch block or terminates with a fatal error.
function divide(float $a, float $b): float {
if ($b === 0.0) {
throw new Exception("Division by zero");
}
return $a / $b;
}
try {
echo divide(4, 0);
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
Understanding the Throwable interface
Both Error and Exception implement Throwable, which provides methods such as getMessage(), getCode(), getFile(), and getTrace(). This means you can catch Throwable to handle both kinds uniformly.
try {
undefined_function();
} catch (Throwable $t) {
echo "Caught: " . $t->getMessage();
}
Throwable is useful in top level error handlers but can obscure root causes if overused. Catch more specific types where possible.
try, catch, finally
The try block wraps code that might throw; catch handles exceptions; finally executes regardless of outcome. Use finally for cleanup such as releasing resources or closing files.
Using multiple catch blocks
Catch blocks are tested in order. Each may specify a different type. The first that matches handles the exception.
try {
risky();
} catch (InvalidArgumentException $e) {
echo "Invalid argument: " . $e->getMessage();
} catch (RuntimeException $e) {
echo "Runtime problem: " . $e->getMessage();
} finally {
echo "Done.";
}
Using union catch syntax
PHP allows multiple types in one catch block with a pipe separator. This is convenient for related errors that share handling logic.
try {
risky();
} catch (LogicException | RuntimeException $e) {
echo "Caught logic or runtime issue: " . $e->getMessage();
}
Re-throwing and wrapping exceptions
You can re-throw an exception with throw or create a new one that wraps the previous via the previous parameter. This preserves the original trace while adding context.
try {
risky();
} catch (Exception $e) {
throw new RuntimeException("Higher level failure", 0, $e);
}
Custom Exception Types
Defining custom exception classes clarifies intent and allows fine-grained handling. They can extend Exception or one of its descendants, adding context or structured data.
Creating domain specific exceptions
Keep exception names descriptive and hierarchical. Group related exceptions under a shared base type.
namespace App\Exception;
class AppException extends \Exception {}
class ValidationException extends AppException {}
class DatabaseException extends AppException {}
Adding extra data to custom exceptions
You can include additional properties or methods that describe the failure more precisely.
class HttpException extends Exception {
public function __construct(
private int $status,
string $message = "",
?Throwable $previous = null
) {
parent::__construct($message, $status, $previous);
}
public function getStatus(): int {
return $this->status;
}
}
throw new HttpException(404, "Not Found");
Error Handlers and set_error_handler()
The error handler converts traditional errors into exceptions or logs them centrally. You can register a custom function with set_error_handler() to intercept PHP errors before default handling.
Registering a basic error handler
The handler receives the level, message, file, and line. Returning true prevents PHP’s internal handler from running. Converting errors to ErrorException simplifies unified exception handling.
set_error_handler(function(
int $level,
string $message,
string $file,
int $line
) {
throw new ErrorException($message, 0, $level, $file, $line);
});
Restoring and suppressing handlers
Use restore_error_handler() to revert to the previous handler. The @ operator suppresses errors for a specific expression, though it is best avoided since it hides useful information.
@unlink("/nonexistent/file.txt"); // Suppresses warning
restore_error_handler();
Handling fatal errors with register_shutdown_function()
Shutdown functions can inspect error_get_last() to detect fatal errors that escaped earlier handling.
register_shutdown_function(function() {
$error = error_get_last();
if ($error && $error["type"] === E_ERROR) {
error_log("Fatal: " . $error["message"]);
}
});
Logging Strategies
Logging preserves diagnostic information without showing it to users. PHP’s error_log() function writes messages to the configured log, and PSR-3 loggers offer structured approaches suitable for larger systems.
Using error_log() for simple logging
This function writes to the system log or a configured file. Supply a custom message type to send via email or socket if required.
error_log("Cache miss for key=$key");
Integrating a PSR-3 compatible logger
Libraries such as Monolog implement the Psr\Log\LoggerInterface. You can inject a logger and use levels like info, warning, or error consistently across your codebase.
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$logger = new Logger("app");
$logger->pushHandler(new StreamHandler(__DIR__ . "/app.log", Logger::INFO));
$logger->info("User login", ["user" => $id]);
$logger->error("Exception caught", ["exception" => $e]);
Structuring and rotating logs
Adopt a consistent format such as JSON lines for easy ingestion. Use rotation tools or Monolog’s RotatingFileHandler to prevent large files. Include request identifiers or correlation IDs for tracing distributed operations.
$logger->pushHandler(new \Monolog\Handler\RotatingFileHandler(
__DIR__ . "/logs/app.log",
7
));