You throw an exception in PHP with throw new Exception('message');. Execution stops at that line and jumps to the nearest catch block that accepts the exception's type, where $e->getMessage() returns the message. Use a specific class, such as InvalidArgumentException, so callers can tell problems apart.
An exception is an object. throw passes it up the call stack to the first caller with a matching catch, and that caller decides what to do; the function that found the problem does not have to know. The mechanics of catching, finally and catching several types are on the try catch page.
What an exception object contains
Every exception carries a message, an integer code, the file and line where it was created, and a stack trace. The constructor takes ($message, $code, $previous), all optional:
getLine() is the line of throw new, not the line of the catch. The trace lists the calls that led there (#0 /home/index.php(8): findOrder()), which is usually the first thing to read when an error shows up in a log. Casting the exception to a string (echo $e; or (string) $e) prints all of it in PHP's standard format.
Built-in exception classes
PHP ships a family of exception classes in its Standard PHP Library (SPL). Throwing the one that names the problem makes your code read better and lets callers catch narrowly. All of these extend Exception:
| Class | Throw it when |
|---|---|
InvalidArgumentException | an argument has the wrong form: an empty name, an unknown option |
DomainException | a value is outside the set that makes sense: a negative price |
OutOfRangeException | the code asked for an index that can never exist (a bug in the caller) |
OutOfBoundsException | a key or index is missing from data only known at runtime |
RangeException | a computed result falls outside the valid range while running |
LengthException | something is too long or too short |
RuntimeException | a problem only detectable while running: a full disk, a timeout |
UnexpectedValueException | a function returned or received a value of a type you did not expect |
LogicException | the code itself is wrong, a bug rather than bad input |
JsonException | thrown by json_encode()/json_decode() with JSON_THROW_ON_ERROR |
InvalidArgumentException, DomainException, LengthException and OutOfRangeException extend LogicException; OutOfBoundsException, RangeException and UnexpectedValueException extend RuntimeException. You can check any class's parents yourself:
TypeError has Error as its parent, not Exception: it belongs to the second family, below.
Errors PHP throws itself
Since PHP 7, and much more so since PHP 8, PHP's own functions and operators throw Error subclasses for problems that used to be warnings. You catch them the same way, but catch (Exception $e) will not match them:
DivisionByZeroError, TypeError, ValueError, ArgumentCountError, UnhandledMatchError and plain Error all extend Error. Each one marks a bug in the calling code more often than bad data, which is why they live outside the Exception tree. declare(strict_types=1) is what makes str_repeat(5, 2) a TypeError; without it PHP would convert 5 to "5".
Write a custom exception class
A one-line subclass is often all you need: the class name itself carries the meaning, and callers can catch exactly that type. When the catcher needs data (which order, which amount), add properties and pass the message up with parent::__construct():
Pick the parent with care: extending RuntimeException (rather than plain Exception) means code that catches RuntimeException also handles yours. A small hierarchy, one base exception per library or module with specific subclasses under it, lets callers choose between "catch everything from payments" and "catch just this one case".
Chain exceptions with previous
When you catch a low-level exception and throw a higher-level one, pass the original as the third argument. Nothing is lost: getPrevious() walks back to the root cause.
The user sees "customers.csv could not be imported"; the log, walking the chain, also gets "row 2 has 2 columns". If a chained exception is never caught, PHP's fatal error prints the whole chain: the original cause first, then each wrapper under a Next line.
throw as an expression
Since PHP 8.0, throw is an expression, so it can sit wherever a value is expected: after ??, in a ternary, in an arrow function. It makes "get this or fail" a single line:
When not to throw
Exceptions are for situations the current function cannot handle and the normal flow does not expect. A search that finds nothing, an optional field that is empty, or a user who is not logged in are ordinary outcomes: return null, false or an empty array and let the caller check, and keep exceptions for the cases where carrying on would be wrong.
Two more habits keep exception code readable: never catch an exception just to ignore it (an empty catch (Exception $e) {} hides the next real bug), and throw specific classes rather than bare Exception, so a caller is never forced to catch everything to handle one case.
Frequently Asked Questions
How do I throw an exception in PHP?
Create an exception object and throw it: throw new InvalidArgumentException('Quantity must be positive');. Execution stops at that line and jumps to the nearest matching catch block; if there is none, the script ends with an "Uncaught" fatal error.
How do I create a custom exception in PHP?
Extend Exception or one of its subclasses: class PaymentFailedException extends RuntimeException {}. That one line is enough to throw and catch it by its own type. Add properties and a constructor when the catcher needs extra data, and call parent::__construct($message, $code, $previous).
What is the difference between getMessage and getCode?
getMessage() returns the text passed as the first constructor argument, meant for humans and logs. getCode() returns the integer passed as the second argument (0 by default), useful for machine checks such as mapping errors to HTTP status codes.
Which exception should I throw in PHP?
Use a built-in SPL class that matches the problem: InvalidArgumentException for a bad argument, DomainException for a value outside what is allowed, RuntimeException for failures only visible at runtime (a full disk, a timeout), LogicException for programming mistakes. For errors your callers need to tell apart, create your own subclass.
What is exception chaining in PHP?
Passing the original exception as the third constructor argument when you throw a new one: throw new ImportException('Import failed', 0, $e);. The new exception gives context, and $e->getPrevious() on it returns the original cause, so no detail is lost.