Chapter 13 ยท Going Deeper
Exceptions & Errors
Things go wrong: the database is down, the user typed "banana" as their age, a file
is missing. Beginner code either crashes with an ugly white screen or hides problems with
die(). Professional code handles trouble gracefully โ with exceptions.
An exception is a fire alarm. When a cook in the kitchen spots a fire, they
don't quietly keep cooking, and they don't burn the restaurant down either โ they
pull the alarm (throw). The alarm travels through the building
until someone trained for emergencies (catch) responds properly: evacuate,
extinguish, apologise to customers. Problem detected in one place, handled in the right place.
Throwing an exception
When your code detects an impossible situation, it throws an exception object โ stopping normal flow instantly:
<?php
class BankAccount {
private float $balance = 0;
public function withdraw(float $amount): void {
if ($amount <= 0) {
throw new InvalidArgumentException("Amount must be positive.");
}
if ($amount > $this->balance) {
throw new Exception("Insufficient funds: balance is K$this->balance.");
}
$this->balance -= $amount;
}
}
Compare with the old chapters where withdraw() just echoed "Insufficient funds".
That was weak: the calling code had no way to know the withdrawal failed. A thrown
exception is unmissable โ ignore it and the program stops.
Catching with try / catch
$account = new BankAccount();
try {
$account->withdraw(500);
echo "Withdrawal successful!"; // this line is skipped if a throw happens above it
} catch (Exception $e) {
echo "Sorry: " . $e->getMessage();
}
echo "<br>The program continues normally.";
Read the flow like a story:
try { ... }โ "attempt this risky block."- The moment a
throwhappens inside, PHP abandons the rest of the try block and jumps to the matchingcatch. catch (Exception $e)โ the exception object lands in$e.$e->getMessage()gives the human-readable reason.- After the catch block, life continues โ no crash.
Catching different problems differently
Exceptions are objects, so they have classes โ and you can catch each type with its own response. PHP checks the catch blocks top to bottom and uses the first that matches:
try {
$account->withdraw(-50);
} catch (InvalidArgumentException $e) {
echo "Input problem: " . $e->getMessage(); // this one matches
} catch (Exception $e) {
echo "General problem: " . $e->getMessage(); // the safety net for everything else
}
Put the most specific exception types first and the general Exception
last. InvalidArgumentException is itself a child of Exception
(inheritance again!), so if the general catch came first, it would swallow everything.
Custom exceptions โ alarms with names
Making your own exception type is delightfully easy: extend Exception. Even an
empty class is valuable, because its name tells the story:
class InsufficientFundsException extends Exception {}
class StudentNotFoundException extends Exception {}
function findStudent(int $id): string {
$register = [1 => "Chanda", 2 => "Mutinta"];
if (!isset($register[$id])) {
throw new StudentNotFoundException("No student with ID $id.");
}
return $register[$id];
}
try {
echo findStudent(99);
} catch (StudentNotFoundException $e) {
echo "Lookup failed: " . $e->getMessage();
}
finally โ the cleanup crew
A finally block runs no matter what โ success or failure. Use it
for cleanup that must always happen (closing files, releasing resources):
try {
echo "Opening report file...<br>";
throw new Exception("Printer on fire!");
} catch (Exception $e) {
echo "Caught: " . $e->getMessage() . "<br>";
} finally {
echo "Closing report file. (This always runs.)";
}
Where to catch? A practical map
- Deep code (classes, models): throw freely. A class should refuse
bad situations loudly โ it should never
echoerror text ordie(), because it can't know whether it's running inside a webpage, an API or a test. - Surface code (the page the user visits): catch, then show a friendly message and log the technical details. Users should see "Something went wrong, please try again" โ never a raw stack trace.
Keep that split in mind โ our final project follows it exactly, and Chapter 15 uses exceptions to handle database failures.
Write a function divide(float $a, float $b) that throws an
InvalidArgumentException with the message "Cannot divide by zero." when
$b is 0, otherwise returns $a / $b. Call it twice inside one
try/catch โ first divide(10, 2), then divide(5, 0) โ printing results
and the caught message.
Show solution
<?php
function divide(float $a, float $b): float {
if ($b == 0) {
throw new InvalidArgumentException("Cannot divide by zero.");
}
return $a / $b;
}
try {
echo divide(10, 2) . "<br>"; // 5
echo divide(5, 0) . "<br>"; // throws โ line below never runs
echo "You will never see this.";
} catch (InvalidArgumentException $e) {
echo "Error: " . $e->getMessage();
}
What happens the moment a throw executes inside a try block?
When does a finally block run?
throw new Exception("reason")pulls the fire alarm;try/catchresponds to it.$e->getMessage()returns the human-readable reason.- Catch specific exception types first, general
Exceptionlast. - Custom exceptions are just
class MyProblem extends Exception {}โ the name is the documentation. finallyalways runs โ for guaranteed cleanup.- Deep code throws; surface code catches, shows friendly messages, and logs the details.