An exception signals "this cannot continue" and unwinds the stack until something catches it.
PHP
<?php
function divide(float $a, float $b): float
{
if ($b === 0.0) {
throw new InvalidArgumentException("Cannot divide by zero");
}
return $a / $b;
}
try {
echo divide(10, 0);
} catch (InvalidArgumentException $e) {
echo "Error: " . $e->getMessage();
} finally {
echo "This always runs";
}Catching several types
PHP
<?php
try {
riskyOperation();
} catch (PDOException $e) {
// database problem
} catch (JsonException | RuntimeException $e) {
// either of these
} catch (Throwable $e) {
// absolutely anything else, including fatal Errors
}Throwable is the root of both Exception (things you can anticipate)
and Error (engine-level problems like TypeError). Catch Throwable
only at the outermost layer, where you log and show a friendly page.
Useful methods
PHP
<?php
$e->getMessage();
$e->getCode();
$e->getFile();
$e->getLine();
$e->getTraceAsString();
$e->getPrevious(); // the exception this one wrappedCustom exceptions
Give distinct failures distinct types so callers can react precisely:
PHP
<?php
class OutOfStockException extends RuntimeException {}
if ($product['stock'] < $qty) {
throw new OutOfStockException("Only {$product['stock']} left");
}Never leak details to visitors
Stack traces and file paths help attackers. Log them; show a generic message.
PHP
<?php
try {
processOrder();
} catch (Throwable $e) {
error_log($e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
http_response_code(500);
echo "Something went wrong. Please try again.";
}Production settings
PHP
<?php
ini_set('display_errors', '0'); // never show errors to visitors
ini_set('log_errors', '1');
error_reporting(E_ALL); // but log everythingOn your own machine, flip display_errors to 1 — seeing the error
immediately is worth far more than a tidy page while you're developing.