laravel-exceptions

Installation
SKILL.md

Laravel Exceptions

A broken business rule is a domain exception, thrown by the action that owns the rule. Each entity has one exception class whose static factories name every way its operations can fail. An exception that can surface from a request implements Symfony's HttpExceptionInterface through the Httpable concern, so it carries its own status code and decides its own reporting, and the exception handler needs no wiring for it.

Rules

  • One class per entity, a named factory per failure. {Entity}Exception in app/Exceptions/. Factories read as the failure: cannot{Verb}() for a business rule (cannotCancel()), failedTo{Verb}() for an operation that went wrong, {thing}NotFound() for something missing that isn't a model. Callers never new the exception or pass it a message.
  • Guards throw the factory. throw_unless($condition, OrderException::cannotCancel()) in the action's guard(). laravel-actions owns guards.
  • Exceptions that can surface from a request implement HttpExceptionInterface and use Httpable. Laravel then renders them with their own status, headers and message.
  • Every HTTP factory passes its status code. Pick the code that fits the failure; there's no fixed table. The concern falls back to 500 for a missing code, which turns an expected failure into a server error.
  • Write the message for the client. Laravel sends an HttpExceptionInterface exception's message to the client even with APP_DEBUG off, 5xx included; every other exception becomes Server Error. Give a sentence the caller can act on, with public identifiers only: a fixed sentence, or sprintf with an (ulid: %s) suffix. Never a path, a query, a provider's raw error or the cause's message.
  • Chain the cause. A factory that wraps a caught exception takes Throwable $previous and passes it as the third constructor argument. The report keeps the original trace; the client sees only your message.
  • Client errors aren't reported during a request; server errors always are. A 4xx during a request is expected: the client has been told and nothing is broken. A 5xx means something is broken whoever called. In a job or scheduled task there's no client, so every failure needs attention. Httpable::report() does this, and it must return true to skip reporting: Laravel treats any return value except false as "handled" and skips its own reporting, the error tracker's hook included.
  • Control-flow exceptions aren't HTTP. An exception thrown and caught inside a job to choose a path extends Exception with no interface and no concern. It never reaches a client.
  • Let the framework map its own failures. A missing model is findOrFail() or route binding (404), a denied policy is AuthorizationException (403), invalid input is a Form Request (422). Don't write factories that duplicate them.
  • Nothing per exception in the ExceptionBooter. It holds the error tracker's hook; the interface and the concern do the mapping, so no render() or report() callbacks for domain exceptions. laravel-providers owns booters.

Example

Installs
55
GitHub Stars
15
First Seen
Mar 27, 2026