Skip to main content
A resource replaces the 15-class CRUD scaffold (5 controllers + 5 requests + 5 responses) with one resource + five controllers. All request schemas, response envelopes, and OpenAPI docs are derived from the resource declaration. This how-to wires up Animal as a fully protected resource (JWT + scopes).

Directory layout

1. Declare the resource

The identifier (animal_uuid) is a regular property declared first by convention. Excluding it from MODE_CREATE removes it from the create request body documentation — the server generates it. tags is a SimpleArrayProperty: a plain list of scalars (["friendly", "indoor"]). Each element is validated against the declared item type and cast on the way in, so $animal->tags is always a string[]. Reach for the object-based ArrayProperty only when the list holds objects.

2. Wire the five controllers

Each controller is a thin subclass. You implement getResourceClass(), buildRoute(), and __invoke().

Create

View

Update

Delete

List

What the framework handles for you

  • Body / path / filter / sort validationRequestValidationMiddleware validates against the runtime documentation derived from AnimalResource. Unknown ?order_by=hacked_field → 422 before your controller runs.
  • Response envelopeResource*Response classes emit the standard {"data": ...} (and "pagination" for list). No toArray() to write.
  • Per-mode field visibilityexcludeFromMode() hides animal_uuid from the create body and weight from list responses; the rest of the endpoints still see both.
  • OpenAPI — all five operations are generated from the single resource. Add a field to AnimalResource::init() and every operation updates at once.

Nested resources

Because buildRoute() is fully explicit, nested URLs require no special setup — add more path segments and declare each one via ->pathProperty():
Both path parameters are validated, documented in OpenAPI, and typed in the path bag.

Optional: IDE autocomplete via the DocBlock generator

Run the docblock generator once. See Generate OpenAPI and docblocks for the script. It produces:
  • @property annotations on AnimalResource — so $animal->name, $animal->status etc. autocomplete.
  • A typed request class per controller that has path params, sorting, filtering, or pagination:
    • AnimalViewRequest / AnimalDeleteRequest — typed path()
    • AnimalUpdateRequest — typed path()
    • AnimalListRequest — typed sorting(), filtering(), paginator()
To activate autocomplete in a controller, add a @param annotation on __invoke with the generated class: