Every application, no matter how simple, can encounter errors — a database connection fails, a file isn't found, input doesn't match the expected format, and much more. PHP provides two mechanisms for handling these situations: Error (PHP-level failures) and Exception (exceptional conditions that are thrown and can be caught). Understanding both is very important for building robust and easy-to-debug applications.
Types of Errors in PHP
PHP has several error levels, including:
- E_ERROR — a fatal error, script execution stops.
- E_WARNING — a warning, the script keeps running.
- E_NOTICE — a minor notice (e.g. an undefined variable).
- E_DEPRECATED — a deprecated function is used.
- E_PARSE — a syntax error, the script can't run.
Configuring Error Display
In a development environment, display all errors. In production, hide them from the user but log them:
<?php
// For a development environment
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// For a production environment
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/path/to/error.log');
error_reporting(E_ALL);
Better yet, set this in the php.ini file or .htaccess and use environment variables to distinguish development and production mode.
The Basic try-catch Block
Exceptions are caught using a try-catch block:
<?php
function divide(int $a, int $b): float
{
if ($b === 0) {
throw new InvalidArgumentException("Cannot divide by zero!");
}
return $a / $b;
}
try {
$result = divide(10, 2);
echo "10 / 2 = $result\n"; // 10 / 2 = 5
$result = divide(10, 0); // This will throw an exception
echo "This line will not be executed";
} catch (InvalidArgumentException $e) {
echo "Error: " . $e->getMessage() . "\n";
echo "In file: " . $e->getFile() . " line " . $e->getLine();
}
Catching Multiple Exception Types
You can have several catch blocks to handle different exception types:
<?php
function processFile(string $path): string
{
if (!file_exists($path)) {
throw new RuntimeException("File not found: $path");
}
$content = file_get_contents($path);
if ($content === false) {
throw new RuntimeException("Failed to read file: $path");
}
if (strlen($content) === 0) {
throw new LengthException("File is empty: $path");
}
return $content;
}
try {
$data = processFile('/data/report.txt');
echo "Content: $data";
} catch (LengthException $e) {
echo "File is empty: " . $e->getMessage();
} catch (RuntimeException $e) {
echo "Runtime error: " . $e->getMessage();
} catch (\Throwable $e) {
// Catch all uncaught errors and exceptions
echo "Unexpected error: " . $e->getMessage();
}
In PHP 8, use \Throwable (not \Exception) as a fallback to catch all types of errors and exceptions.
The finally Block
The finally block is always executed, whether an exception occurs or not — useful for cleaning up resources:
<?php
function readFile(string $path): void
{
$handle = fopen($path, 'r');
if ($handle === false) {
throw new RuntimeException("Cannot open the file.");
}
try {
while (($line = fgets($handle)) !== false) {
echo $line;
}
} finally {
// Always close the file even if an exception occurs
fclose($handle);
echo "\nFile has been closed.";
}
}
Creating a Custom Exception
For a more structured application, create your own exception classes:
<?php
// Custom exception definition
class DatabaseException extends RuntimeException
{
private string $query;
public function __construct(string $message, string $query = '', int $code = 0, ?\Throwable $previous = null)
{
$this->query = $query;
parent::__construct($message, $code, $previous);
}
public function getQuery(): string
{
return $this->query;
}
}
class ValidationException extends \InvalidArgumentException
{
private array $errors;
public function __construct(array $errors)
{
$this->errors = $errors;
parent::__construct("Validation failed: " . implode(', ', $errors));
}
public function getErrors(): array
{
return $this->errors;
}
}
// Usage
try {
throw new ValidationException(['Name is required', 'Email is invalid']);
} catch (ValidationException $e) {
foreach ($e->getErrors() as $error) {
echo "- $error\n";
}
}
Global Error Handler
To catch PHP errors (not exceptions) globally and turn them into exceptions:
<?php
// Turn all errors into ErrorException so they can be caught with try-catch
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool {
throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
});
// Catch uncaught exceptions
set_exception_handler(function (\Throwable $e): void {
error_log("Uncaught Exception: " . $e->getMessage() . " in " . $e->getFile());
// Show a user-friendly error page
http_response_code(500);
echo "An error occurred. Our team is working on this issue.";
});
Conclusion
Good error and exception handling makes your PHP application more robust, easier to debug, and provides a better experience for users. Use the try-catch-finally block to handle exceptional conditions, create custom exceptions for domain-specific errors, and always distinguish between error display in development and production environments. With this approach, bugs are easier to find and the application can fail gracefully.