Exception handling: typed exceptions vs error codes
In PHP the standard is to throw typed exceptions. Some teams use error codes (return value or error object) for domain errors. Where do you draw the line?
Exceptions for exceptional conditions: database is down, required resource not found, precondition violated. Expected domain outcomes (user not found in a lookup) should be a return value: nullable, Option type, or Result type.
The test for “use exception vs return value”: would a caller realistically want to continue execution if this happens without catching? If yes, return a value. If the only sane response is to bail, throw.
In API controllers, validation failures are better as return values because the controller needs to format the error response. Using exceptions for validation (Laravel does this with HttpException) works but blurs the control flow.
Over-using exceptions as flow control is a PHP antipattern. Throwing and catching is expensive compared to returning a value. For loops or repeated operations, exceptions in the hot path matter.
Typed exception hierarchies (UserNotFoundException, PermissionDeniedException, etc.) are useful for API exception handlers that translate domain exceptions to HTTP responses. One handler per exception type, no type-checking switch.
We have a ValidationException that carries field-level errors. It is thrown by validation service classes and caught by a global exception handler that formats the 422 response. Exception as control flow, but at a well-defined layer boundary.
```php blocks are runnable.