Pet: Create, View, Update, Delete. That’s 4 controllers + 4 requests + 4 responses = 12 classes. (List is a natural fifth — add it the same way.)
Directory layout
Shared pieces
Everything below assumes JWT auth using the sameBearerAuth scheme across all four routes — swap in your authenticator of choice.
getRoute() method — they’re split out here to keep the snippets compact.
Create
CreatePetRequest
CreatePetResponse
CreatePetController
__invoke() parameter and the documented responses from the new expressions its body returns, so 403 and 422 land in the spec without being listed here. Build a response anywhere but in __invoke() and it is left out of the spec. See Controller.
View
ViewPetRequest
No request-specific properties needed — the path parameter is declared on the route.
ViewPetResponse
ViewPetController
Update
UpdatePetRequest
Only body properties live in the request class. The path parameter is declared on the route.
UpdatePetResponse
UpdatePetController
Delete
DeletePetRequest
No request-specific properties needed — the path parameter is declared on the route.
DeletePetResponse
For idempotent deletes you can return the built-in DeletedApivalkResponse (204) instead of writing your own. If you want to emit a confirmation payload, write one like the others:
DeletePetController
When to use this over a resource
Use the manual variant when:- Payloads differ between endpoints — e.g.
CreateacceptsspeciesbutUpdatedoesn’t, or the view response returns an expanded shape with joined data. - Responses need custom envelopes —
{"data": ...}is fine for resources, but a legacy API might expect{"pet": ...}with metadata at the root. - You want complete control over validation — per-endpoint
getDocumentation()gives you surgical control over required/optional fields, descriptions, and examples.