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}Exceptioninapp/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 nevernewthe exception or pass it a message. - Guards throw the factory.
throw_unless($condition, OrderException::cannotCancel())in the action'sguard(). laravel-actions owns guards. - Exceptions that can surface from a request implement
HttpExceptionInterfaceand useHttpable. 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
HttpExceptionInterfaceexception's message to the client even withAPP_DEBUGoff, 5xx included; every other exception becomesServer Error. Give a sentence the caller can act on, with public identifiers only: a fixed sentence, orsprintfwith 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 $previousand 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 returntrueto skip reporting: Laravel treats any return value exceptfalseas "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
Exceptionwith 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 isAuthorizationException(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 norender()orreport()callbacks for domain exceptions. laravel-providers owns booters.