ApiExposable - viames/pair GitHub Wiki
Pair framework: ApiExposable
Pair\Api\ApiExposable is a trait for ActiveRecord models used by CrudController.
It defines API behavior such as filtering, sorting, searchable fields, includes, pagination limits, validation rules, and the explicit response contract used by Pair v4 CRUD endpoints.
When to use
Use this trait on ActiveRecord models that must be exposed through auto-CRUD endpoints with strict, explicit query and output policies.
Main methods
apiConfig(): array
Override this in your model to define API configuration.
getApiConfig(): array
Returns merged config (apiConfig() + framework defaults) as a legacy-compatible array.
getCrudResourceConfig(): CrudResourceConfig
Returns the same merged config as a typed Pair\Api\CrudResourceConfig value object for internal CRUD consumers.
The normalized config is cached per model class through CrudResourceMetadata.
isFilterable(string $property): bool
isSortable(string $property): bool
isSearchable(string $property): bool
isIncludable(string $relation): bool
Convenience checks against merged configuration.
Pair v4 response contract
The preferred Pair v4 configuration is readModel.
This makes the public API payload explicit and reusable instead of exposing the persistence object shape directly.
Example:
<?php
namespace App\Models;
use Pair\Orm\ActiveRecord;
use Pair\Api\ApiExposable;
use App\Api\ReadModels\FaqReadModel;
class Faq extends ActiveRecord {
use ApiExposable;
public const TABLE_NAME = 'faqs';
/**
* Return the explicit CRUD API policy and response contract.
*
* @return array<string, mixed>
*/
public static function apiConfig(): array
{
return [
'readModel' => FaqReadModel::class,
'searchable' => ['question', 'answer'],
'sortable' => ['createdAt', 'position'],
'filterable' => ['category', 'isPublished'],
'includes' => ['author'],
'includeReadModels' => [
'author' => \App\Api\ReadModels\AuthorReadModel::class,
],
'includePreloader' => \App\Api\Preloaders\FaqIncludePreloader::class,
'perPage' => 20,
'maxPerPage' => 100,
'defaultSort' => '-createdAt',
'rules' => [
'create' => [
'question' => 'required|string|min:3',
'answer' => 'required|string|min:3',
],
'update' => [
'question' => 'string|min:3',
'answer' => 'string|min:3',
],
],
];
}
}
Migration bridge: resource
resource classes are still supported as a bridge for Pair v3 to v4 migrations.
Use them when the application already owns a stable transformer and you are not ready to introduce a proper readModel yet.
For new Pair v4 code, prefer readModel.
Query behavior enabled by this config
Example request:
GET /api/faqs?search=payment&sort=-createdAt&filter[isPublished]=1&page=2&perPage=20
CrudController + QueryFilter will enforce only declared searchable, sortable, and filterable fields.
Typed config normalization
Pair v4 normalizes apiConfig() through CrudResourceConfig.
This keeps array configuration source-compatible while centralizing defaults for pagination, validation rules, includes, include preloaders, read models, and legacy resource adapters.
Pair caches normalized model metadata for the current PHP process. If a test, generator, or long-running command changes model API metadata and keeps running in the same process, call:
\Pair\Api\CrudResourceMetadata::clear(\App\Models\Faq::class);
Common pitfalls
- exposing internal fields in
filterableorsortable - forgetting to cap
maxPerPage - keeping the output contract implicit instead of defining
readModel
See also: CrudController, ReadModel, RecordMapper, CrudResourceConfig, CrudResourceMetadata, QueryFilter, Upgrade-to-v4