Skip to content
Viames Marino edited this page Oct 19, 2026 · 3 revisions

Pair framework: Input

Pair\Http\Input is the immutable request input object used by the explicit Pair v4 web controller path.

It gives controllers one small object for:

  • HTTP method
  • query-string values
  • form or JSON body values
  • normalized request headers
  • merged typed accessors

Use Request for JSON API modules that extend Pair\Api\ApiController. Use Input for web modules that extend Pair\Web\Controller.

Constructor example

use Pair\Http\Input;

$input = new Input(
	'post',
	['status' => 'draft', 'page' => '2'],
	['status' => 'published', 'featured' => 'true'],
	['content-type' => 'application/json']
);

$input->method(); // POST
$input->query('status'); // draft
$input->body('status'); // published
$input->string('status'); // published
$input->int('page'); // 2
$input->bool('featured'); // true
$input->header('Content-Type'); // application/json

Merged accessors read from query + body, with body values taking precedence.

From globals

Input::fromGlobals() reads $_SERVER, $_GET, and $_POST. When there is no $_POST payload and the request advertises application/json, it parses the raw JSON body into the body array.

use Pair\Http\Input;

$input = Input::fromGlobals();

if ($input->method() === 'PATCH') {
	$changes = $input->only(['name', 'email', 'isActive']);
}

Invalid or empty JSON bodies are treated as an empty body array. The object does not store validation errors; validate at the controller or form layer. JSON scalars and null also become an empty body array. A JSON object or list is accepted as an array. To supply a raw body in a test, use Input::fromGlobals($rawBody); the content-type check and precedence of non-empty $_POST still apply.

In a Pair v4 web controller

use Pair\Web\Controller;
use Pair\Web\PageResponse;

require_once __DIR__ . '/classes/OrdersDefaultPageState.php';

/**
 * Orders module controller using the explicit Pair v4 request path.
 */
final class OrdersController extends Controller {

	/**
	 * Render the list page with typed query-string state.
	 */
	public function defaultAction(): PageResponse {

		$input = $this->input();

		$state = new OrdersDefaultPageState(
			status: $input->string('status', 'open'),
			page: max(1, $input->int('page', 1))
		);

		return $this->page('default', $state, 'Orders');

	}

}

Common typed reads

$search = $input->string('search', '');
$page = $input->int('page', 1);
$active = $input->bool('active');
$tags = $input->array('tags');
$hasSort = $input->has('sort');
$slice = $input->only(['search', 'page', 'sort']);

string(), int(), and bool() return the provided default when the merged value is a composite value such as an array or a non-stringable object. This avoids ambiguous scalar casts for malformed query or body data such as ?search[]=term.

has() returns true for explicitly present falsy values such as 0, false, and ''. It returns false only when the merged value is null.

Casting boundaries

Typed accessors convert values; they are not validators or HTML escaping helpers.

Accessor Current behavior
string() Casts scalar and Stringable values; falls back for null, arrays, and other objects. An empty string remains an empty string.
int() Falls back for null, '', and non-scalars; otherwise applies a PHP integer cast. A non-numeric string such as abc becomes 0, even if another default was supplied.
bool() Trims and lowercases scalar or Stringable values. Accepts 1, true, yes, on and 0, false, no, off; unrecognized values return the default.
array() Returns arrays unchanged, uses the default for null, and wraps any other value in a one-element array.
only() Selects non-null merged values by name; it does not cast or validate them.

For a value that must come specifically from the URL, read query() rather than a merged accessor. Body fields can override query values, including with null. Apply domain checks before persistence and escape values when rendering HTML.

Notes

  • method() always returns uppercase.
  • fromGlobals() normalizes header names to lowercase. When calling the constructor directly, supply lowercase header keys yourself.
  • header() lowercases the lookup name, so lookups are case-insensitive when the stored keys are normalized.
  • query(), body(), and value() return the full array when called without a key.
  • Input is immutable; create a new object if tests or adapters need a different request shape.

See also: Controller, PageResponse, Request, API.

Clone this wiki locally