Request - viames/pair GitHub Wiki
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 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:
requiredstringintnumericemailboolmin:Nmax: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 explicitApiErrorResponsevalidateObjectOrResponse()returns aRequestDataobject or an explicitApiErrorResponsevalidate()remains the legacy bridge and still responds immediately withINVALID_FIELDS- missing optional fields are skipped
- validated arrays are not cast automatically
That last point matters:
intaccepts digit strings, but you should still cast to(int)yourselfboolacceptstrue,false,1,0,'1','0','true','false', but it does not cast the result to a PHP boolean for you- decimal
min:Nandmax:Nbounds 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(), idempotencyKey(), isReplayRequest()
bearerToken() extracts Authorization: Bearer ... when present.
idempotencyKey() supports both:
Idempotency-KeyX-Idempotency-Key
isReplayRequest() returns true when X-Pair-Replay is 1 or true.
$token = $this->request->bearerToken();
$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_ADDRby default - trusts
ForwardedandX-Forwarded-Foronly whenREMOTE_ADDRbelongs toPAIR_TRUSTED_PROXIES - supports exact proxy IPs and CIDR ranges in
PAIR_TRUSTED_PROXIES - prefers the standardized
Forwardedheader 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()andjson()are lazy and cached.header()handles special server variables such asContent-TypeandContent-Length.all()is convenient, but remember that JSON overrides same-named query parameters.
Common pitfalls
- Assuming JSON is always present on
POST,PUT, orPATCH. - 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, ApiResponse, Idempotency, ThrottleMiddleware.