Exceptions

An exception is an object a function throws when it cannot finish, and it unwinds the call stack until some caller catches it.

Syntaxthrow new InvalidArgumentException('age must be digits');

A function sometimes cannot do what its name promises. The file is not on disk. The age arrives as the word thirty. Returning false for that puts the burden on every caller, and callers forget to look.

An exception is the other answer. It is an object that describes the failure. throw hands it upwards: PHP leaves the current function at once, then the function that called it, and so on, until it reaches a catch that matches the class thrown. Code after the throw never runs.

What every exception carries

Everything you can throw implements Throwable, so everything answers the same questions.

MethodGives you
getMessage()the text it was created with
getCode()an integer code, 0 unless set
getFile(), getLine()where it was created
getTrace()the calls in progress, innermost first
getTraceAsString()the same list as one string
getPrevious()the earlier failure that caused this one, or null

Reading a trace

Frame 0 is the function that threw. The frame above it is its caller, and that is usually where the bad value came from, so that is usually the line to fix.

Two families exist. Exception is for failures your code reports. Error is for failures the engine reports, such as TypeError. Both implement Throwable, and one that nobody catches ends the script.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Stop a function returning a half-valid value when its input makes no sense.
  • Find the real line behind a 500 by reading the trace instead of guessing.
  • Decide whether a failure is yours to report or the engine's, by catching Exception or Error.

More examples

What an uncaught exception looks like

Nothing caught the exception, so it reached the top and became a fatal error. Read the trace from the bottom up: {main} is the file's top level, #0 is the call that failed, and the 'thrown in' line is where the object was created. The exit status is non-zero, which is how a deploy script notices. Since PHP 8.5 other fatal errors print a backtrace in this shape too, controlled by the fatal_error_backtraces setting.

Example · php
<?php

declare(strict_types=1);

function loadTemplate(string $name): string
{
    throw new RuntimeException("template {$name} is missing");
}

loadTemplate('invoice');

// PHP stops the script and writes, in one form or another:
//
// PHP Fatal error:  Uncaught RuntimeException: template invoice is missing
//   in /srv/app/render.php:7
// Stack trace:
// #0 /srv/app/render.php(10): loadTemplate('invoice')
// #1 {main}
//   thrown in /srv/app/render.php on line 7

The trace as one string, for a log

getTraceAsString() flattens the trace into the numbered '#0 file(line): function()' form, which is what you want in a log file. Use it rather than printing the exception to the page: the trace contains file paths and argument values, and a visitor has no business seeing either.

Example · php
<?php

declare(strict_types=1);

try {
    throw new DomainException('order 4412 has no lines');
} catch (DomainException $e) {
    error_log($e::class . ': ' . $e->getMessage());
    error_log($e->getTraceAsString());
}

Exceptions you did not write

Most exceptions you meet come from the standard library. json_decode() reports a parse failure as JsonException when you pass JSON_THROW_ON_ERROR, and intdiv() has thrown DivisionByZeroError for a zero divisor ever since it was added in PHP 7.0, as has the % operator. It was the / operator that changed in PHP 8.0, from emitting a warning to throwing that same DivisionByZeroError. JsonException extends Exception; DivisionByZeroError extends ArithmeticError, which extends Error. Both are catchable the same way.

Example · php
<?php

declare(strict_types=1);

try {
    $data = json_decode('{not json', true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    echo 'bad JSON: ', $e->getMessage(), "\n";
}

try {
    echo intdiv(10, 0);
} catch (DivisionByZeroError $e) {
    echo $e::class, ': ', $e->getMessage(), "\n";
}

// bad JSON: Syntax error
// DivisionByZeroError: Division by zero

Discussion

  • Be the first to comment on this lesson.