try, catch and throw
In the previous lesson, a thrown DivisionByZeroError object made it all the way up, and the program stopped. Back to the courier: an undelivered parcel goes up the stairs until someone opens a door. In code, that door is a catch block, and you can put it wherever you know what to do about the failure.
Not only PHP can throw objects; your code can too. An object that code throws to report a failure is called an exception. There are ready-made classes for this, for example InvalidArgumentException, "a wrong argument".
throw: throwing an exception
Let a function take money from an account that must not go below zero:
<?php
function withdraw(int $balance, int $amount): int
{
if ($amount > $balance) {
throw new InvalidArgumentException('not enough money');
}
return $balance - $amount;
}
try {
echo withdraw(100, 30), "\n"; // 70
echo withdraw(100, 500), "\n";
} catch (InvalidArgumentException $e) {
echo 'error: ', $e->getMessage(), "\n"; // error: not enough money
}Let's go through it step by step:
throw new InvalidArgumentException('not enough money')creates an object with a message and throws it.- The first call passes the check and returns
70. - In the second call the condition is true.
throwcuts the function short: it never reachesreturn, and the call has no value. - The exception leaves
withdrawand arrives in the code that called it. The line withechoin thetryblock does not finish. - PHP checks the
catch: the class matches, the object lands in the variable$e, and the body ofcatchruns.
If there were no try around the call, the exception would go further: to whoever called that code, then to the next caller, and so on all the way up. The chain of calls that have not finished yet is called the stack, and this walk back along it is called unwinding the stack. Interviewers like to ask what runs after throw: in the try block, nothing more, and after the catch block the program goes on as usual.
The catch block
A caught exception is a value
The variable $e holds an object, and you can ask it for details:
$e->getMessage()—the message passed when it was created;$e->getCode()—a numeric error code if one was passed as the second argument, otherwise0.
The first matching catch
One try can have several catch blocks. PHP checks them from top to bottom and runs the first one whose class matches:
<?php
try {
throw new RuntimeException('disk is full', 507);
} catch (InvalidArgumentException $e) {
echo 'bad argument', "\n";
} catch (Exception $e) {
echo $e->getMessage(), ' ', $e->getCode(), "\n"; // disk is full 507
}
echo 'next line', "\n"; // next lineA RuntimeException is not an InvalidArgumentException, so the first catch is skipped. The second one matches: catch (Exception $e) catches the class Exception and its child classes, and RuntimeException and InvalidArgumentException are both its descendants. That is why the specific classes go higher and the general Exception goes lower.
Here the door picture stops working. A real door opens for any courier, while a catch opens only for its own class and that class's descendants.
Several classes in one catch
When different failures are handled the same way, list their classes with |:
<?php
$people = 0;
try {
echo intdiv(100, $people), "\n";
} catch (InvalidArgumentException | DivisionByZeroError $e) {
echo 'cannot share: ', $e->getMessage(), "\n"; // cannot share: Division by zero
}The division by zero threw a DivisionByZeroError, and the same catch that catches InvalidArgumentException caught it. The program did not stop; it printed a clear message.
Common mistake
The word new is forgotten after throw:
<?php
$name = '';
if ($name === '') {
throw InvalidArgumentException('empty name'); // ✗
}Fatal error: Uncaught Error: Call to undefined function InvalidArgumentException()Without new, PHP reads InvalidArgumentException('empty name') as a call to a function with that name. There is no such function, so PHP throws its own error instead of yours. Only an object can be thrown, so you need throw new InvalidArgumentException('empty name');.
Key points
throw new …throws an exception: the remaining lines of thetryblock and of the function do not run.- An exception travels back along the chain of calls to the first matching
catch, and after thecatchthe program keeps running. catch (Exception $e)catchesExceptionand all its child classes;catchblocks are checked from top to bottom.$e->getMessage()and$e->getCode()tell you what happened;catch (A | B $e)catches two classes at once.