01 · THE IDEAREAD → TRY → LEARN
Why exceptions exist
A function sometimes receives input it cannot work with. You could return false, -1 or an empty string, but then every caller has to remember that special value and check for it. If one caller forgets, the bad value flows onward as if it were real data.
An exception solves this by interrupting normal flow. When you use throw, the function stops immediately. PHP then unwinds the call stack until it finds a matching catch block. Valid results are returned normally, and failures travel through a separate channel that cannot be mistaken for data.
The three pieces
throwcreates the failure. It needs an object that is anExceptionorError(or a subclass of one of them).trywraps the code that might fail.catch (ClassName $e)handles a matching failure.$e->getMessage()returns the text you supplied.
Here is a first worked example. Notice that the second call never prints anything, because the exception leaves the try block before echo runs. Execution then resumes after the catch block.
function parsePercent(int $value): int
{
if ($value < 0 || $value > 100) {
throw new InvalidArgumentException("Percent must be 0-100");
}
return $value;
}
try {
echo parsePercent(40), "\n";
echo parsePercent(140), "\n";
} catch (InvalidArgumentException $e) {
echo "Caught: ", $e->getMessage(), "\n";
}
echo "Done\n";40 Caught: Percent must be 0-100 Done
Choose a meaningful type
PHP ships with many exception classes. InvalidArgumentException fits a bad argument value, and OutOfBoundsException fits a missing key. You can also define your own subclass when you want callers to react to one specific problem.
Catch blocks are checked from top to bottom, and the first one that matches the thrown class handles it. A catch for a parent class also catches its children. So place narrow classes before broad ones.
A finally block runs whether or not an exception happened, which makes it useful for cleanup or for always recording that a step finished. The next example uses a custom subclass and a loop to show both paths.
class EmptyNameException extends InvalidArgumentException {}
function greet(string $name): string
{
$name = trim($name);
if ($name === '') {
throw new EmptyNameException('Name is required');
}
return 'Hello, ' . $name;
}
foreach (['Ada', ' '] as $input) {
try {
echo greet($input), "\n";
} catch (EmptyNameException $e) {
echo 'Invalid: ', $e->getMessage(), "\n";
} finally {
echo "Checked\n";
}
}Hello, Ada Checked Invalid: Name is required Checked
throw is an expression
Since PHP 8.0, throw can appear anywhere an expression is allowed. That makes compact guards possible, for example on the right side of the null coalescing operator ??. If the key exists you get its value; otherwise the exception is thrown.
function requireKey(array $data, string $key): string
{
return $data[$key] ?? throw new OutOfBoundsException("Missing key: $key");
}
$config = ['host' => 'localhost'];
echo requireKey($config, 'host'), "\n";
try {
echo requireKey($config, 'port'), "\n";
} catch (OutOfBoundsException $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}localhost OutOfBoundsException: Missing key: port
What not to do
- Do not leave an exception uncaught unless you want the script to stop. An uncaught exception ends in a fatal error, unless a handler registered with
set_exception_handler()deals with it. - Do not catch
Errorobjects to hide bugs. They usually signal a programming mistake that should be fixed, not silenced. - Do not throw an exception inside a
catchblock and expect the next siblingcatchto handle it. Sibling blocks only see exceptions from thetrybody. - Do not use exceptions for ordinary, expected branching such as "item not found in a search" when a plain return value is clearer.
Your exercise
Write safeIntDivide(int $a, int $b): array. It should use a helper that throws InvalidArgumentException with the message Division by zero when $b is 0, and otherwise returns intdiv($a, $b). The public function catches that exception and always returns an array:
- on success:
['ok' => true, 'value' => <quotient>] - on failure:
['ok' => false, 'error' => <message>]
The caller never sees an exception, and a valid quotient of 0 is clearly different from a failure because the ok flag says which one happened. Check the boundaries: a zero numerator, a negative numerator, and a zero divisor.
Recap
Throw when a function cannot honestly produce a result, catch where you know how to respond, and choose a specific exception class so the handler can be precise. Keep success and failure on separate paths.
Functions · Conditionals
- Throw and catch a typed exception
- Keep invalid inputs separate from valid results
Sources and PHP version
Tested on PHP 8.5.10.