-
Notifications
You must be signed in to change notification settings - Fork 2
CrudController
Pair\Api\CrudController extends ApiController and auto-exposes REST-style CRUD endpoints for ActiveRecord models.
Use it when your API module mainly maps HTTP verbs to database-backed resources and you want filtering, pagination, sparse fieldsets, and resource transformation without writing the same controller code repeatedly.
Register resources in _init():
<?php
namespace App\Modules\Api;
use Pair\Api\CrudController as BaseCrudController;
use App\Orm\Faq;
class ApiController extends BaseCrudController {
protected function _init(): void
{
parent::_init();
$this->crud('faqs', Faq::class);
}
}This generates the following endpoints for faqs:
GET /api/faqsGET /api/faqs/{id}POST /api/faqs-
PUT /api/faqs/{id}andPATCH /api/faqs/{id} DELETE /api/faqs/{id}
If $config is null and the model exposes getApiConfig(), CrudController uses that model-level API configuration automatically.
The list flow applies:
- filtering, sorting, searching, and pagination through QueryFilter
- optional sparse fieldsets via
fields - optional includes via
include
The response is sent through ApiResponse::paginated().
The show flow:
- loads the object with
find($id) - returns
NOT_FOUNDif it does not exist - applies
fieldsandincludeif requested and allowed
The create flow:
- requires JSON content
- optionally validates against
rules.create - writes only properties that exist in the model binds
- returns the created resource with HTTP
201
The update flow:
- requires JSON content
- optionally validates against
rules.update - updates only bindable properties
- returns the transformed resource after update
The delete flow:
- loads the object
- calls
isDeletable()when available - returns
204on success
If a resource config defines a resource class and that class exists, CrudController uses it to transform the output. Otherwise it falls back to ActiveRecord::toArray().
That makes it easy to keep a stable public contract even when the model contains internal fields.
Example:
$this->crud('users', \App\Orm\User::class, [
'resource' => \App\Api\Resources\UserResource::class,
]);Common config keys used with crud():
rules.createrules.updatefilterablesortablesearchabledefaultSortperPagemaxPerPageincludesresource
Example with rules, includes, and transformer:
$this->crud('users', \App\Orm\User::class, [
'rules' => [
'create' => [
'email' => 'required|email',
'name' => 'required|string|max:120',
],
'update' => [
'email' => 'email',
'name' => 'string|max:120',
],
],
'filterable' => ['email', 'enabled', 'groupId'],
'sortable' => ['id', 'email', 'createdAt'],
'searchable' => ['email', 'name', 'surname'],
'defaultSort' => '-id',
'includes' => ['group'],
'resource' => \App\Api\Resources\UserResource::class,
]);protected function _init(): void
{
parent::_init();
$this->crud('users', \App\Orm\User::class, [
'filterable' => ['email', 'enabled', 'groupId'],
'sortable' => ['id', 'email', 'createdAt'],
'searchable' => ['email', 'name', 'surname'],
'defaultSort' => '-id',
'perPage' => 20,
'maxPerPage' => 100,
'includes' => ['group'],
]);
$this->crud('orders', \App\Orm\Order::class, [
'filterable' => ['status', 'customerId'],
'sortable' => ['id', 'createdAt', 'total'],
'defaultSort' => '-createdAt',
]);
}CrudController only intercepts missing actions that match a registered slug. Regular controller methods still work:
public function statsAction(): void
{
$this->requireAuth();
\Pair\Api\ApiResponse::respond([
'users' => \App\Orm\User::countAllObjects(),
'orders' => \App\Orm\Order::countAllObjects(),
]);
}If you allow include=group, the model should expose a matching getter such as getGroup():
$this->crud('users', \App\Orm\User::class, [
'includes' => ['group'],
]);At runtime, CrudController calls getGroup() and appends the transformed related data when the include is requested.
-
getRegisteredResources(): arrayreturns the list of registered slugs. -
getResourceConfig(string $slug): ?arrayreturns the full registration entry for that slug, including bothclassandconfig. -
__call(mixed $name, mixed $arguments): voidroutes missing actions to CRUD handlers when the action name matches a registered slug; otherwise it falls back to the normalApiControllerNOT_FOUNDbehavior.
Runtime inspection example:
$slugs = $this->getRegisteredResources();
$usersRegistration = $this->getResourceConfig('users');
// ['class' => ..., 'config' => ...]- Registering a model class that is not a valid
ActiveRecord. - Making
filterabletoo broad and exposing internal columns. - Forgetting a
resourcetransformer when you need a strict public contract. - Assuming includes are automatic without declaring them in config.
- Expecting
fieldsorincludeto bypass your transformer; they apply after or around the configured transformation flow.
See also: API, ApiController, QueryFilter, Resource, ApiExposable.