<?php OOP: Zero to Hero Chapter 13 of 20

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.

๐Ÿšจ
Real-world analogy

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.";
Sorry: Insufficient funds: balance is K0. The program continues normally.

Read the flow like a story:

  1. try { ... } โ€” "attempt this risky block."
  2. The moment a throw happens inside, PHP abandons the rest of the try block and jumps to the matching catch.
  3. catch (Exception $e) โ€” the exception object lands in $e. $e->getMessage() gives the human-readable reason.
  4. 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
}
Input problem: Amount must be positive.
Order matters

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();
}
Lookup failed: No student with ID 99.

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.)";
}
Opening report file... Caught: Printer on fire! Closing report file. (This always runs.)

Where to catch? A practical map

Keep that split in mind โ€” our final project follows it exactly, and Chapter 15 uses exceptions to handle database failures.

โœ๏ธ Try it yourself

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();
}
5 Error: Cannot divide by zero.
Quick quiz

What happens the moment a throw executes inside a try block?

Quick quiz

When does a finally block run?

๐Ÿ”‘ Key points
  • throw new Exception("reason") pulls the fire alarm; try/catch responds to it.
  • $e->getMessage() returns the human-readable reason.
  • Catch specific exception types first, general Exception last.
  • Custom exceptions are just class MyProblem extends Exception {} โ€” the name is the documentation.
  • finally always runs โ€” for guaranteed cleanup.
  • Deep code throws; surface code catches, shows friendly messages, and logs the details.