Exception Hierarchy & Custom Exceptions
debt(d5/e3/b5/t5)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan and psalm as tools, both specialist static analysis tools. The code pattern (catch(Exception $e) everywhere; throwing generic \Exception) is detectable by these tools but not by the compiler or default linters — it requires running dedicated static analysis.
Closest to 'simple parameterised fix' (e3). The quick_fix describes creating domain-specific exception classes extending a base app exception — a straightforward class-creation pattern that replaces generic throws at each call site. It's more than a one-line patch but is a localised refactor within the error-handling layer, not cross-cutting architectural work.
Closest to 'persistent productivity tax' (b5). The applies_to covers web, cli, and queue-worker contexts, meaning the absence of a proper exception hierarchy affects all application work streams. Every new feature must either follow or fight the established exception pattern, imposing an ongoing tax on error handling design across the entire codebase without being fully architectural in scope.
Closest to 'notable trap' (t5). The misconception field explicitly states that developers believe 'throwing generic Exception with a message is equivalent to a named exception class.' This is a well-documented gotcha — competent developers from backgrounds with less strict typing conventions commonly reach for generic exceptions with descriptive messages, not realising callers lose the ability to catch specific error types independently without parsing strings.
Also Known As
TL;DR
Explanation
PHP's SPL provides a rich base exception hierarchy: RuntimeException, LogicException, InvalidArgumentException, OutOfRangeException, DomainException, and more. Custom exceptions should extend the most specific applicable SPL base class, be named after the domain condition they represent (InsufficientFundsException, UserNotFoundException), and live in a dedicated exceptions namespace. Callers can catch specific exceptions for recovery or catch the SPL base for logging. Avoid overusing generic Exception — it forces callers to catch everything or nothing. Exception messages should be informative for developers, not end users.
Common Misconception
Why It Matters
Common Mistakes
- Throwing generic RuntimeException or Exception everywhere — callers cannot distinguish error types without parsing the message.
- Creating exception classes with no added properties — at minimum add context via constructor arguments.
- Not organising exceptions into a hierarchy — a base DomainException with specific subclasses enables both specific and broad catching.
- Catching the base Exception in application code — catching too broadly swallows unexpected errors.
Code Examples
throw new Exception('Something went wrong'); // too generic
throw new InsufficientFundsException(
"Balance \${$balance} insufficient for \${$amount} transfer"
);