ApiResponse - viames/pair GitHub Wiki

Pair framework: ApiResponse

Pair\Api\ApiResponse standardizes JSON responses for Pair API endpoints.

It provides:

  • a built-in error registry (ApiResponse::ERRORS)
  • runtime custom error registration
  • small helpers for success, error, and paginated payloads
  • explicit response builders for v4-style response returns

Main methods (deep dive)

1) ApiResponse::respond(array|stdClass|null $data, int $httpCode = 200): void

Use respond() for normal JSON responses with an explicit HTTP code.

use Pair\Api\ApiResponse;

ApiResponse::respond(['saved' => true], 201);

This is the most flexible helper and the one you should use when the response shape is already known.

Important current behavior: the underlying Utilities::jsonResponse() treats empty payloads as no-content responses. In practice, ApiResponse::respond(null, 201) is emitted as HTTP 204, even if you passed 201.

2) ApiResponse::error(string $errorCode, array $extra = []): void

Use error() for standardized API errors.

Current behavior:

  • looks up custom registered errors first
  • falls back to built-in ERRORS
  • if the code is unknown, falls back to INTERNAL_SERVER_ERROR
  • keeps only string keys from the $extra array before sending the payload
ApiResponse::error('BAD_REQUEST', [
    'detail' => 'Missing field',
]);

Validation example:

ApiResponse::error('INVALID_FIELDS', [
    'errors' => [
        'email' => 'The field email must be a valid email address',
        'age' => 'The field age must be an integer',
    ],
]);

Conflict example:

ApiResponse::error('CONFLICT', [
    'detail' => 'Order is already paid',
]);

2.1) ApiResponse::errorResponse(string $errorCode, array $extra = []): ApiErrorResponse

Use errorResponse() when you want the same registry-driven error semantics as error(), but need to return an explicit response object instead of sending JSON immediately.

use Pair\Api\ApiResponse;

return ApiResponse::errorResponse('BAD_REQUEST', [
    'detail' => 'Missing field',
]);

2.2) ApiResponse::jsonResponse(mixed $data, int $httpCode = 200): JsonResponse

Use jsonResponse() when you need the same payload semantics as respond(), but want to return an explicit response object instead of sending JSON immediately.

return ApiResponse::jsonResponse(['saved' => true], 201);

JsonResponse can also carry scalar JSON values for replay or bridge cases, although arrays, objects, read models, and null remain the normal API shapes.

2.3) ApiResponse::successResponse(?string $message = null): JsonResponse

Use successResponse() for lightweight acknowledgements that should still travel through the explicit v4 response path.

return ApiResponse::successResponse('Done');

3) ApiResponse::paginated(array $data, int $page, int $perPage, int $total): void

Use paginated() when the response must include standard paging metadata.

The output includes:

  • data
  • meta.page
  • meta.perPage
  • meta.total
  • meta.lastPage
$page = max(1, (int)$this->request->query('page', 1));
$perPage = max(1, min(100, (int)$this->request->query('perPage', 20)));
$total = 187;
$rows = ['id' => 1], ['id' => 2](/viames/pair/wiki/'id'-=>-1],-['id'-=>-2);

ApiResponse::paginated($rows, $page, $perPage, $total);

3.1) ApiResponse::paginatedResponse(array $data, int $page, int $perPage, int $total): JsonResponse

Use paginatedResponse() when a list endpoint should return the standard data/meta envelope as an explicit response object.

return ApiResponse::paginatedResponse($rows, $page, $perPage, $total);

4) ApiResponse::success(?string $message = null): void

Use this when you only need a simple success acknowledgment and do not care about a custom status code or payload shape.

ApiResponse::success('Done');

5) ApiResponse::registerErrors(array $errors): void

Use registerErrors() to add application-specific error codes or override built-in ones intentionally.

ApiResponse::registerErrors([
    'ORDER_ALREADY_SHIPPED' => [
        'httpCode' => 409,
        'message' => 'Order already shipped',
    ],
]);

ApiResponse::error('ORDER_ALREADY_SHIPPED', ['orderId' => 2241]);

Another common pattern:

ApiResponse::registerErrors([
    'PROFILE_NOT_COMPLETE' => [
        'httpCode' => 409,
        'message' => 'User profile is incomplete',
    ],
]);

Secondary behavior

  • ApiResponse::ERRORS is the built-in error-code dictionary.
  • Custom registered errors take precedence over built-in ones if keys collide.
  • ApiResponse::error() builds an explicit ApiErrorResponse internally before sending it, then preserves the legacy terminate-after-send behavior.
  • respond(), success(), and paginated() delegate to their explicit response builders before sending, then preserve the legacy terminate-after-send behavior.
  • Explicit response objects such as JsonResponse do not terminate execution by themselves; the v4 dispatcher decides when the request is complete.

Practical notes

  • Prefer error() over hand-written JSON error payloads so your API keeps a stable vocabulary.
  • Prefer respond() when you need a custom status code such as 201 or 204.
  • Prefer the *Response() builders when you are migrating an endpoint toward explicit Pair v4 responses.
  • If the payload is null or otherwise empty, verify the final status code you want, because the current implementation promotes it to 204.
  • Prefer paginated() for list endpoints so clients always get the same metadata shape.

Common pitfalls

  • Sending unknown error codes without registering them and expecting custom semantics.
  • Echoing text or HTML before calling ApiResponse::*, which breaks JSON responses.
  • Letting every endpoint invent its own error names instead of keeping a stable shared registry.

See also: API, JsonResponse, ApiErrorResponse, Request, Idempotency, CrudController.