- Throw and catch exceptions with
throw,try,catchandfinally - Explain the
Throwable,ExceptionandErrorhierarchy and write a custom exception - Configure error reporting correctly for development and production
The payment system does not respond, a user sends broken JSON, the database is unreachable — on a real website such things happen every day. A good program does not show a “white screen”: it catches the problem, gives the user a clear message and writes the details to a log. In PHP the main tool for this is exceptions.
try, catch and finally
When code detects a problem, it throws an exception with throw: the function stops at once, and the exception travels up the chain of calls until it finds a catch block that catches it. Risky code goes into a try block, catch catches a specific type of exception, and finally runs in every case — whether an exception happened or not.
<?php
function divide(int $a, int $b): float
{
if ($b === 0) {
throw new InvalidArgumentException("Cannot divide by zero");
}
return $a / $b;
}
foreach ([4, 0] as $b) {
try {
$result = divide(12, $b);
echo "12 / $b = $result\n";
} catch (InvalidArgumentException $e) {
echo "Error: ", $e->getMessage(), "\n";
} finally {
echo "-- done with $b\n";
}
}12 / 4 = 3 -- done with 4 Error: Cannot divide by zero -- done with 0
When $b = 0, the echo line never ran: after throw, control jumped straight to catch. finally ran on both passes of the loop. Useful methods of an exception object: getMessage() — the message, getCode() — the code, getFile() and getLine() — where the error happened, getPrevious() — the previous exception.
Exception and Error
Since PHP 7 the common parent of everything that can be thrown is the Throwable interface. It has two branches: **Exception — problems in the program's own logic (a bad argument, a missing file, a failed database connection), and Error** — PHP's internal errors (a wrong type, division by zero, calling a method that does not exist).
| Branch | Common classes | When |
|---|---|---|
Exception | InvalidArgumentException, RuntimeException, JsonException, PDOException | expected problems: catch and handle them |
Error | TypeError, ValueError, DivisionByZeroError, ArgumentCountError, UnhandledMatchError | usually a bug in the code: fix it |
<?php
$tests = [
fn() => intdiv(10, 0),
fn() => json_decode("{bad json", flags: JSON_THROW_ON_ERROR),
fn() => throw new RuntimeException("Disk is full"),
];
foreach ($tests as $test) {
try {
$test();
} catch (Throwable $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}
}DivisionByZeroError: Division by zero JsonException: Syntax error RuntimeException: Disk is full
Your own exceptions
To create your own exception class, extend an existing one, usually RuntimeException or DomainException. Let the name describe the problem clearly and, if needed, keep extra data in a property. Then a catch block can react precisely to exactly this situation.
<?php
class InsufficientFundsException extends RuntimeException
{
public function __construct(public readonly float $missing)
{
parent::__construct("Not enough money: missing $missing AZN");
}
}
class Wallet
{
public function __construct(private float $balance) {}
public function pay(float $amount): void
{
if ($amount > $this->balance) {
throw new InsufficientFundsException($amount - $this->balance);
}
$this->balance -= $amount;
echo "Paid $amount, left {$this->balance}\n";
}
}
$wallet = new Wallet(30);
try {
$wallet->pay(12);
$wallet->pay(25.5);
$wallet->pay(1);
} catch (InsufficientFundsException $e) {
echo $e->getMessage(), "\n";
echo "Top up at least ", ceil($e->missing), " AZN\n";
}Paid 12, left 18 Not enough money: missing 7.5 AZN Top up at least 8 AZN
$data = json_decode($input);
if ($data === null) {
// broken JSON or the value null?
echo json_last_error_msg();
}try {
$data = json_decode($input, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
echo "Invalid JSON: ", $e->getMessage();
}null can mean either an error or a valid JSON value (null). An exception reports the error unambiguously.Warnings and error reporting
Some problems do not throw an exception but just give a warning: for example, a missing variable produces Warning: Undefined variable $x, and an outdated function produces a Deprecated message. Which messages appear is decided by php.ini settings. The golden rule: during development see everything; in production show the user nothing and write everything to the log.
Setting (php.ini) | Development | Production |
|---|---|---|
error_reporting | E_ALL | E_ALL |
display_errors | On | Off |
log_errors | On | On |
error_reporting(E_ALL); ini_set('display_errors', '1');Key points
throwthrows an exception;tryholds risky code,catchcatches a specific type andfinallyalways runs.Throwablehas two branches:Exception(expected problems) andError(PHP's internal errors).- Extend an existing class for your own exception and give it a meaningful name; keep extra data in a property.
- In PHP 8
throwis an expression:$x = $arr[$k] ?? throw new ...; - Use
display_errors = Onin development, andOfftogether withlog_errors = Onin production.
Check yourself
10 questions. Every correct answer earns XP.
finally block run?