Skip to content

Request

Viames Marino edited this page May 10, 2026 · 6 revisions

Pair framework: Request

Pair\Api\Request is the HTTP wrapper used by Pair API controllers.

It covers:

  • method and header access
  • lazy JSON body parsing
  • query parameter access
  • lightweight validation rules
  • request-object mapping through RequestData
  • bearer token, API session, and idempotency-key extraction
  • trusted-proxy-aware client IP resolution

Main methods (deep dive)

1) Basic accessors: method(), header(), query(), all()

Use these when the controller needs to inspect the request shape quickly.

$method = $this->request->method(); // GET, POST, PUT...
$auth = $this->request->header('Authorization');
$contentType = $this->request->header('Content-Type');
$page = $this->request->query('page', 1);
$allQuery = $this->request->query();

all() merges $_GET and the parsed JSON body, with JSON values taking precedence when the same key exists in both places:

// GET /api/users?page=2 with JSON body {"status":"active"}
$all = $this->request->all();
// ['page' => '2', 'status' => 'active']

2) Body helpers: rawBody(), json(), isJson()

rawBody() reads php://input lazily and caches it.

json() parses the request body as JSON and returns either the whole associative array or a single key:

$body = $this->request->json();
$email = $this->request->json('email');

isJson() checks whether the content type includes application/json.

if (!$this->request->isJson()) {
    return \Pair\Api\ApiResponse::errorResponse('UNSUPPORTED_MEDIA_TYPE', [
        'expected' => 'application/json',
    ]);
}

Practical note: the current implementation expects the decoded JSON body to be an array. Invalid JSON, empty bodies, or scalar JSON payloads effectively behave like null.

3) Validation: validateOrResponse(), validateObjectOrResponse(), validate(), and requireFields()

Supported rules:

  • required
  • string
  • int
  • numeric
  • email
  • bool
  • min:N
  • max:N

Example:

$data = $this->request->validate([
    'email' => 'required|email|max:120',
    'age' => 'int|min:18',
    'newsletter' => 'bool',
]);

If you are moving a controller to the explicit v4 flow, use validateOrResponse() and return the error object instead of terminating immediately:

$result = $this->request->validateOrResponse([
    'email' => 'required|email|max:120',
    'age' => 'int|min:18',
]);

if ($result instanceof \Pair\Api\ApiErrorResponse) {
    return $result;
}

$data = $result;

Current behavior:

  • validateOrResponse() returns only validated keys or an explicit ApiErrorResponse
  • validateObjectOrResponse() returns a RequestData object or an explicit ApiErrorResponse
  • validate() remains the legacy bridge and still responds immediately with INVALID_FIELDS
  • missing optional fields are skipped
  • validated arrays are not cast automatically

That last point matters:

  • int accepts digit strings, but you should still cast to (int) yourself
  • bool accepts true, false, 1, 0, '1', '0', 'true', 'false', but it does not cast the result to a PHP boolean for you
  • decimal min:N and max:N bounds are compared numerically when both the value and the rule parameter are numeric

Required-only shortcut:

$data = $this->request->requireFields(['email', 'password']);

3.1) Mapping to request objects

For custom endpoints, prefer a small RequestData object when the action needs typed, normalized input.

$payload = $this->request->validateObjectOrResponse(\App\Api\Requests\CreateOrderRequest::class, [
    'customerId' => 'required|int',
    'amount' => 'required|numeric|min:0.01',
    'currency' => 'required|string|max:3',
]);

if ($payload instanceof \Pair\Api\ApiErrorResponse) {
    return $payload;
}

// $payload is now a CreateOrderRequest instance.

4) Auth and delivery helpers: bearerToken(), sessionIdentifier(), idempotencyKey(), isReplayRequest()

bearerToken() extracts Authorization: Bearer ... when present. The scheme is parsed case-insensitively and uses the same Authorization fallback keys as the OAuth bootstrap code.

sessionIdentifier() returns the legacy sid query parameter when present, otherwise it reads the X-Pair-Session header. New clients should prefer X-Pair-Session when they still need session-based API calls, because it avoids putting session IDs in URLs.

Authorization parsing is delegated to AuthorizationHeader, so API request code and OAuth bootstrap code share the same Basic and Bearer parsing behavior.

idempotencyKey() supports both:

  • Idempotency-Key
  • X-Idempotency-Key

isReplayRequest() returns true when X-Pair-Replay is 1 or true.

$token = $this->request->bearerToken();
$sessionId = $this->request->sessionIdentifier();
$key = $this->request->idempotencyKey();

if (!$token) {
    return \Pair\Api\ApiResponse::errorResponse('AUTH_TOKEN_MISSING');
}

Replay-aware example:

if ($this->request->isReplayRequest()) {
    // optional branch for offline queue or retried mobile requests
}

5) ip(): string

ip() returns the effective client IP.

Current behavior:

  • uses REMOTE_ADDR by default
  • trusts Forwarded and X-Forwarded-For only when REMOTE_ADDR belongs to PAIR_TRUSTED_PROXIES
  • supports exact proxy IPs and CIDR ranges in PAIR_TRUSTED_PROXIES
  • prefers the standardized Forwarded header when available

Example .env:

PAIR_TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8,192.168.0.0/16

Example:

$ip = $this->request->ip();
$limiterKey = 'throttle:login:' . $ip;

Secondary behavior

  • rawBody() and json() are lazy and cached.
  • header() handles special server variables such as Content-Type and Content-Length.
  • all() is convenient, but remember that JSON overrides same-named query parameters.

Common pitfalls

  • Assuming JSON is always present on POST, PUT, or PATCH.
  • Forgetting that all() lets JSON override same-named query keys.
  • Treating array validation as automatic type casting.
  • Putting endpoint normalization in comments instead of in a RequestData::fromArray() implementation.
  • Trusting forwarded headers without configuring PAIR_TRUSTED_PROXIES.

See also: API, RequestData, AuthorizationHeader, ApiResponse, Idempotency, ThrottleMiddleware.

Clone this wiki locally