PHP Exception
Learn PHP exceptions: how to throw and catch errors with try/catch/finally, the Exception class methods, multi-catch, and writing custom exception classes.
What is an Exception?
An exception is an object that represents an error or an unexpected condition that interrupts the normal flow of a program. Instead of letting a problem crash the script silently, you throw an exception at the point where something goes wrong, then catch it somewhere that knows how to recover, log, or report it.
This page covers how to throw and catch exceptions with try/catch/finally, the methods every exception object gives you, how to catch several exception types, and how to write your own exception classes.
Exceptions are useful whenever a function cannot meaningfully continue: invalid user input, a failed database connection, a missing file, or a value outside the allowed range. They keep the error-handling logic separate from the normal logic, so the "happy path" stays readable.
throw new Exception('Something went wrong');In PHP 7 and later, both Exception and Error implement the Throwable interface, so anything that can be thrown is a Throwable.
Throwing and Catching with try / catch / finally
You wrap risky code in a try block. If a statement inside throws, PHP stops running the rest of the try block and jumps to the first matching catch. The optional finally block always runs afterwards — whether an exception was thrown or not — which makes it the right place to release resources (close a file, a database handle, a lock).
<?php
function divide($a, $b) {
if ($b === 0) {
throw new InvalidArgumentException('Division by zero is not allowed.');
}
return $a / $b;
}
try {
echo divide(10, 2), "\n"; // 5
echo divide(10, 0), "\n"; // throws — the next line never runs
} catch (InvalidArgumentException $e) {
echo 'Caught: ' . $e->getMessage() . "\n";
} finally {
echo "Done.\n";
}Output:
5
Caught: Division by zero is not allowed.
Done.Notice that the second echo divide(...) never prints, because the throw aborts the try block immediately. The finally block still runs.
Reading Information from an Exception
Every exception object carries useful details. The most common methods, all inherited from the base Exception class, are:
| Method | Returns |
|---|---|
getMessage() | The human-readable error message |
getCode() | The integer error code you passed to the constructor |
getFile() | The file where the exception was created |
getLine() | The line number where it was created |
getTraceAsString() | The call stack as a string, useful for logging |
getPrevious() | The previous exception, when one wraps another |
<?php
class InsufficientFundsException extends Exception {}
class Account {
private float $balance;
public function __construct(float $balance) { $this->balance = $balance; }
public function withdraw(float $amount): void {
if ($amount > $this->balance) {
throw new InsufficientFundsException(
"Cannot withdraw $amount; balance is {$this->balance}.",
100 // a custom error code
);
}
$this->balance -= $amount;
}
}
$account = new Account(50);
try {
$account->withdraw(80);
} catch (InsufficientFundsException $e) {
echo $e->getMessage() . "\n"; // Cannot withdraw 80; balance is 50.
echo 'Code: ' . $e->getCode() . "\n"; // Code: 100
}Output:
Cannot withdraw 80; balance is 50.
Code: 100Catching Multiple Exception Types
A single try can have several catch blocks, checked top to bottom — the first one whose type matches the thrown exception wins. If two unrelated exception types should be handled the same way, combine them in one block with the | (pipe) operator instead of duplicating code.
<?php
function parseAge(string $input): int {
if (!is_numeric($input)) {
throw new TypeError("'$input' is not a number.");
}
$age = (int) $input;
if ($age < 0) {
throw new RangeException("Age cannot be negative.");
}
return $age;
}
foreach (['42', 'abc', '-5'] as $value) {
try {
echo parseAge($value) . "\n";
} catch (TypeError | RangeException $e) {
echo get_class($e) . ': ' . $e->getMessage() . "\n";
}
}Output:
42
TypeError: 'abc' is not a number.
RangeException: Age cannot be negative.Order matters: because PHP exceptions form a hierarchy, put more specific types first and broader ones (like Exception or Throwable) last, otherwise the broad block will catch everything before the specific one gets a chance.
Creating Custom Exceptions
Beyond the built-in classes, you can define your own exception types by extending Exception (or a more specific built-in like RuntimeException). A dedicated class makes your catch blocks expressive — you can react to your error specifically — and lets you attach extra data.
In the Account example above, InsufficientFundsException is a custom exception. Often an empty subclass is enough; add methods only when you need extra behaviour:
<?php
class ValidationException extends Exception {
private array $errors;
public function __construct(string $message, array $errors = []) {
parent::__construct($message);
$this->errors = $errors;
}
public function getErrors(): array {
return $this->errors;
}
}If you override the constructor, always call parent::__construct() so the message, code, and previous exception are set up correctly.
Catching
Throwable. To handle both ordinary exceptions and engine-level errors (such as aTypeErrorfrom a wrong argument type), catchThrowable. It is the safest "catch-all", but use it as a last resort so you don't accidentally swallow bugs you should fix:try { // risky code } catch (Throwable $e) { error_log($e->getMessage()); }
Best Practices
- Catch only what you can handle. Let exceptions you can't recover from propagate up to a central handler.
- Throw early, catch late. Throw at the exact point a value becomes invalid; catch where you can actually respond.
- Use specific types. Custom or built-in subclasses beat a bare
Exceptionfor control flow. - Write informative messages, and use
getCode()for machine-readable categorisation. - Log, don't silence. Record exceptions with
error_log()instead of swallowing them. For uncaught exceptions, register a global handler withset_exception_handler(). - Don't use exceptions for normal flow. Reserve them for genuinely exceptional conditions.
How Control Flows
The diagram below shows the path execution takes through a try/catch/finally structure:
graph TD
Try[Try Block] -->|No Exception| Finally[Finally Block]
Try -->|Exception Thrown| Catch[Catch Block]
Catch --> Finally