Skip to content

CkRequest

Job: the current request, read once and held for the rest of it, with a typed reader for every kind of value — nothing to make, nothing to pass, nothing to call first.

The first member called reads the request PHP is serving — the method and the content type from $_SERVER, the raw query string, a JSON body from php://input (the one place PHP keeps it; $_POST only ever holds a form), a form or multipart body — straight from what PHP was handed, never $_REQUEST, and every later call reads what was read. Every reader is the owning class's reader with the entry already in hand, the same parameters after the input, the same rules and the same reasons, and a dotted key walks nested maps as in CkDictionary. The everyday kinds are one call — Bool, String, and Html for a field written in an editor — and a nested value comes back as a bag, Map and Maps, for CkDictionary to read on; that is all of CkDictionary this class repeats, so a list of values is CkDictionary::Strings over Bag()->Body(). A number, a date and an identifier are first the thing, then what it does: Number hands the entry back with every number reader of CkNumber and its OrNull twin on it, CkRequest::Number('id')->Integer(), CkRequest::Number('qty')->SmallIntOrNull(), so the value is judged by what will hold it; Date with Carbon, CarbonDay and their twins, Uuid with ToUuid and its twin.

Two parts, never merged. The query string is read on every method, the body on every method but GET and HEAD, and a reader takes the request's own part: the query string on GET and HEAD, the body on the rest. So on a POST, CkRequest::Number('id')->Integer() is the body's id, and nothing in the address can stand in for it — the trick $_REQUEST falls for. Bag() hands the two parts over as the standard bag each, the part chosen at the end of the call — Bag()->Body() for a validator or CkSentinel::ToNull, Bag()->Query() for the address asked for by name on a POST, CkDictionary::Integer(CkRequest::Bag()->Query(), 'page') — and the method that decided which is the request's own, Bag()->Method(), a RequestMethod; the request headers are one more bag on it, Bag()->Headers(), each under its name in lowercase with hyphens, the cookies another, Bag()->Cookies(), and the address the connection came from is Bag()->ClientAddress().

One request per request. A request has one payload, and it does not change while it is being processed, so there is nothing to load and nothing to refresh: PHP serves one request per process, the request is read once and kept. A worker runtime that serves many requests from one process — Swoole, RoadRunner, FrankenPHP — repopulates $_SERVER for each, and the request is read again as soon as its fingerprint there changes, so one request is never answered with another's data. On a host that adds backslashes to what it hands PHP, as WordPress does, CkSettings::RequestUnslash says so once. A test sets $_SERVER and $_POST as the web server would, which is what every example on this page does first.

Quick look

In a handler, the request is already there:

// no-check
use SafeCoder\CommonKit\CkDictionary;
use SafeCoder\CommonKit\CkEmail;
use SafeCoder\CommonKit\CkRequest;

$id = CkRequest::Number('id')->Integer();                       // the body's "id", on a POST
$email = CkEmail::Address(CkRequest::String('email'));
$newsletter = CkRequest::Bool('newsletter');                // a checkbox: "on" is true
$bio = CkRequest::Html('bio');                              // a field written in an editor: the text out of the markup
$page = CkDictionary::Integer(CkRequest::Bag()->Query(), 'page');   // the address, asked for by name

A test sets the request as the web server would, and the first call reads it:

use SafeCoder\CommonKit\CkDictionary;
use SafeCoder\CommonKit\CkEmail;
use SafeCoder\CommonKit\CkRequest;
use SafeCoder\CommonKit\Enums\RequestMethod;

$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$_SERVER['QUERY_STRING'] = 'page=2';
$_POST = ['id' => '7', 'email' => 'ANA@EXAMPLE.COM', 'qty' => '3.7', 'newsletter' => 'on', 'tags' => ['a', 'b'], 'bio' => '<p>Hello <b>world</b></p>'];

CkRequest::Number('id')->Integer();                            // -> 7   (first a number, then what it does)
CkEmail::Address(CkRequest::String('email'));              // -> 'ana@example.com'
CkRequest::Number('qty')->Integer();                       // -> 4   ("3.7" rounds, as CkNumber::Integer reads it)
CkRequest::Number('qty')->SmallIntOrNull(exact: true);     // -> null   (the kind chosen at the end; a fraction refused, null instead of a fallback)
CkRequest::Number('id')->BigInt();                         // -> 7
CkRequest::Bool('newsletter');                             // -> true
CkRequest::Html('bio');                                    // -> 'Hello world'   (the text out of the markup)
CkDictionary::Strings(CkRequest::Bag()->Body(), 'tags');   // -> ['a', 'b']   (a list: CkDictionary's reader over the bag)
CkDictionary::Integer(CkRequest::Bag()->Query(), 'page');      // -> 2   (the address)
CkRequest::Number('page')->Integer();                          // -> 0   (the body is a POST's own part, and it has no page)
CkRequest::MissingKeys('id', 'email', 'country');          // -> ['country']
CkRequest::Bag()->Method() === RequestMethod::Post;        // -> true

Methods

Bag

The request as it was read — two parts as the standard bag each, the method, the headers, the cookies, the client address.

static function Bag(): CkRequestBag

Returns the request as a CkRequestBag, never null, with its two parts on it — each the array every CkDictionary reader, a validator and CkSentinel::ToNull take, as the request was read — the method that decided which of them is the request's own, the headers and the cookies as one more bag each, and the address the connection came from:

On it Answers Empty, or null, when
Body() The body — a JSON or form body, or the multipart fields on POST GET and HEAD, which carry no body the readers read; a body that could not be read
Query() The query string, on any method The address carried none, or it could not be read
Method() The method as a RequestMethod case — GET, HEAD, POST, PUT, PATCH, DELETE or OPTIONS, in any case declared null: none was declared, as on the command line, or one the enum does not name — WebDAV's PROPFIND — which is read as a method that carries a body
Headers() The request headers, each under its name in lowercase with hyphens — content-type, x-signature, authorization — and its value as received: $_SERVER's HTTP_* entries, CONTENT_TYPE and CONTENT_LENGTH with them, and an Authorization the web server moved aside or parsed into PHP_AUTH_USER and PHP_AUTH_PW put back; in name order None was sent, as on the command line
Cookies() The cookies as PHP parsed them into $_COOKIE, by name — a cookie written as prefs[lang] a nested array, as PHP builds it — in name order, unslashed with the rest under CkSettings::RequestUnslash None was sent, as on the command line
ClientAddress() The address the connection came from — REMOTE_ADDR, read through CkIP::Address, so canonical: 2001:DB8::1 is '2001:db8::1'. The connection's own address, never a proxy's claim: x-forwarded-for is a header in Headers(), and whether to trust it is yours '': none was reported, as on the command line, or what was reported is not an address

Never null, never throws. The two parts are never merged — a form cannot be tampered with from the address, which is the trick $_REQUEST falls for: on a POST the body is the request's own part, the one the flat readers read, and the address is asked for by name through Query(). This is the door back into the rest of the library: a validator that reports every problem at once takes Bag()->Body(), and so does the saving hook.

use SafeCoder\CommonKit\CkDictionary;
use SafeCoder\CommonKit\CkRequest;
use SafeCoder\CommonKit\Enums\RequestMethod;

$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$_SERVER['QUERY_STRING'] = 'page=2&id=99';
$_SERVER['HTTP_X_SIGNATURE'] = 'sha256=3f2a';
$_SERVER['REMOTE_ADDR'] = '203.0.113.9';
$_COOKIE = ['session' => 'abc'];
$_POST = ['id' => '7'];

CkRequest::Bag()->Method() === RequestMethod::Post;   // -> true
CkDictionary::String(CkRequest::Bag()->Headers(), 'x-signature');   // -> 'sha256=3f2a'   (a header, by lowercase name)
CkDictionary::String(CkRequest::Bag()->Cookies(), 'session');       // -> 'abc'   (a cookie, by name)
CkRequest::Bag()->ClientAddress();                 // -> '203.0.113.9'   (the connection's address, never a proxy's claim)
CkRequest::Bag()->Body();                          // -> ['id' => '7']
CkRequest::Bag()->Query();                         // -> ['page' => '2', 'id' => '99']
CkDictionary::Integer(CkRequest::Bag()->Body(), 'id');     // -> 7   (and it feeds CkDictionary as it is)
CkDictionary::Integer(CkRequest::Bag()->Query(), 'id');    // -> 99   (the address, never merged into the body)
CkRequest::Number('id')->Integer();                    // -> 7   (a POST's own part is the body)

$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['QUERY_STRING'] = 'page=2';
CkRequest::Bag()->Method()?->value;                // -> 'GET'
CkRequest::Bag()->Query();                         // -> ['page' => '2']
CkRequest::Bag()->Body();                          // -> []   (a GET has no body part)

Bool

The library's one boolean rule, on a field.

static function Bool(
    ?string $key = null,
    bool $fallback = false,
    bool $throwIfInvalid = CkSettings::DefaultThrowIfInvalid
): bool
Parameter Type Default What it does
key ?string null The key, matched exactly; tried once more trimmed when the exact key is absent, and read as a dotted path through nested maps when it holds a dot.
fallback bool false What comes back when the entry is missing or unreadable.
throwIfInvalid bool CkSettings::DefaultThrowIfInvalid (false) Pass true to throw a CkRequestException that says why, instead of answering the fallback.

Returns

When Default With throwIfInvalid: true
A real boolean, a word (true/t/yes/y/on, false/f/no/n/off), or exactly 1 or 0 true or false The same
Rejected: missing entry, null, or anything else — "published", 2 The fallback — never false for an unreadable value by accident Throws CkRequestException

CkDictionary::Bool over the part's bag, which reads through CkBool::FromValue. A checkbox arrives as "on", and is true.

use SafeCoder\CommonKit\CkRequest;

$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$_POST = ['newsletter' => 'on', 'terms' => 'yes', 'vip' => 'maybe'];

CkRequest::Bool('newsletter');                     // -> true
CkRequest::Bool('terms');                          // -> true
CkRequest::Bool('vip');                            // -> false   ("maybe" is unreadable: the fallback, not a verdict)
CkRequest::Bool(key: 'vip', fallback: true);       // -> true

Date

A date, with the kind chosen at the end of the call.

static function Date(
    ?string $key = null
): CkRequestDate
Parameter Type Default What it does
key ?string null The key, matched exactly; tried once more trimmed when the exact key is absent, and read as a dotted path through nested maps when it holds a dot.

Returns the entry as a CkRequestDate, never null, with these on it — each the CkDate reader of the same name over the entry found, with the same parameters after the input, the same rules and the same reasons:

On it Answers Missing entry Parameters after the input
Carbon(…) A moment, a Carbon in UTC — CkDate::ToCarbon The fallback read as a moment, or the year-one sentinel order, format, assumeZone, treatSentinelsAsMissing, fallback, throwIfInvalid
CarbonOrNull(…) The same, or null for anything unusable — CkDate::ToCarbonOrNull null order, format, assumeZone, treatSentinelsAsMissing
CarbonDay(…) A calendar day, a CkDay at midnight UTC — CkDate::ToCarbonDay The fallback read as a day, or the year-one sentinel order, format, assumeZone, zone, treatSentinelsAsMissing, fallback, throwIfInvalid
CarbonDayOrNull(…) The same, or null — CkDate::ToCarbonDayOrNull null order, format, assumeZone, zone, treatSentinelsAsMissing

The lookup is done once, here, and the object answers for that entry alone. The OrNull twins never throw; with throwIfInvalid: true the other two raise a CkRequestException naming the key, with the CkDateException behind it. First the date, then what it does: there is no flat Carbon on this class.

use SafeCoder\CommonKit\CkRequest;
use SafeCoder\CommonKit\Enums\DateOrder;

$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$_POST = ['sentAt' => '2026-08-31T10:00:00+02:00', 'birthday' => '21/07/1990', 'since' => '1900-01-01', 'seenAt' => ''];

CkRequest::Date('sentAt')->Carbon();                                       // -> Carbon 2026-08-31T08:00:00Z
CkRequest::Date('birthday')->CarbonDay(order: DateOrder::DayFirst);        // -> CkDay 1990-07-21T00:00:00Z
CkRequest::Date('birthday')->CarbonDayOrNull(order: DateOrder::DayFirst);  // -> CkDay 1990-07-21T00:00:00Z
CkRequest::Date('since')->CarbonDayOrNull();                               // -> null   (a placeholder is missing — for a nullable date column)
CkRequest::Date('since')->CarbonDay();                                     // -> Carbon 0001-01-01T00:00:00Z   (the sentinel, which is no day)
CkRequest::Date('seenAt')->CarbonOrNull();                                 // -> null
CkRequest::Date('gone')->CarbonOrNull();                                   // -> null

Explain

Why the request could not be read — or nothing.

static function Explain(): string

Returns

When Result
The request's own part was read, even to an empty bag ''
It could not be The reason, in the request's own words or the reader's: 'no request method', 'no query in the request', 'no body', 'no content type', 'a multipart body is read on POST only', 'a text/plain body is not one the reader knows', 'not valid JSON'

Never throws. A request the readers cannot read is not an error here — every reader answers its fallback — but this says what was wrong with it, which a log line wants.

use SafeCoder\CommonKit\CkRequest;

$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['QUERY_STRING'] = 'page=2';
CkRequest::Explain();                              // -> ''

$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['QUERY_STRING'] = '';
CkRequest::Explain();                              // -> 'no query in the request'

$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_TYPE'] = 'text/plain';
CkRequest::Explain();                              // -> 'a text/plain body is not one the reader knows'

$_SERVER['REQUEST_METHOD'] = 'PUT';
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
CkRequest::Explain();                              // -> 'a multipart body is read on POST only'

Has

Present, or absent — a null value counts as present.

static function Has(
    ?string $key = null
): bool
Parameter Type Default What it does
key ?string null The key, matched exactly; tried once more trimmed when the exact key is absent, and read as a dotted path through nested maps when it holds a dot.

Returns

When Result
The part holds an entry under the key, whatever its value — null included true
It does not, or the key is null false

Never throws. What a reader's fallback cannot tell you: whether the field was sent at all. CkDictionary::Has over the part's bag.

use SafeCoder\CommonKit\CkDictionary;
use SafeCoder\CommonKit\CkRequest;

$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$_SERVER['QUERY_STRING'] = 'page=2';
$_POST = ['coupon' => '', 'address' => ['zip' => '02134']];

CkRequest::Has('coupon');                          // -> true   (sent, empty)
CkRequest::Has('address.zip');                     // -> true
CkRequest::Has('page');                            // -> false   (the query is not the body)
CkDictionary::Has(CkRequest::Bag()->Query(), 'page');   // -> true

Html

The plain text out of markup — a field written in an editor.

static function Html(
    ?string $key = null,
    int $maxLength = CkSettings::MaxTextInputLength,
    bool $truncate = true,
    mixed $fallback = null,
    bool $throwIfInvalid = CkSettings::DefaultThrowIfInvalid
): string
Parameter Type Default What it does
key ?string null The key, matched exactly; tried once more trimmed when the exact key is absent, and read as a dotted path through nested maps when it holds a dot.
maxLength int CkSettings::MaxTextInputLength (50,000) Maximum length of the extracted text. Values below 1 fall back to the default.
truncate bool true Cut over-long text to fit (the default) instead of rejecting it. The cut never splits a character.
fallback mixed null What comes back, read the same way, when the entry is missing, not text, or nothing is left once the markup is gone; an empty string when null.
throwIfInvalid bool CkSettings::DefaultThrowIfInvalid (false) Pass true to throw a CkRequestException that says why, instead of answering the fallback.

Returns

When Default With throwIfInvalid: true
The entry holds text, with markup or without The extracted text, as CkText::Html writes it: script and style bodies discarded, block tags line breaks, other tags spaces, entities decoded — and nothing else done to it The same
Rejected: missing entry, null, a value that is not text — a list, a map — nothing left once the markup is gone, or over the limit with truncate off The fallback, read the same way; '' when none Throws CkRequestException

CkText::Html over the entry found — the one reader here that is not CkDictionary's, since the bag has no reader of that kind. String strips the same markup and then tidies the text: invisible characters out, runs of whitespace collapsed. Html leaves the text as the markup left it, line breaks included — for a description, a comment or a biography written in an editor that produces HTML, when the text is what will be stored. Text extraction, not an XSS boundary: encode at render time.

use SafeCoder\CommonKit\CkRequest;

$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$_POST = ['bio' => '<p>Hello <b>world</b></p>', 'note' => '<script>alert(1)</script>Hi &amp; bye', 'blank' => '<p></p>'];

CkRequest::Html('bio');                                   // -> 'Hello world'
CkRequest::Html('note');                                  // -> 'Hi & bye'   (the script body gone, the entity decoded)
CkRequest::Html('blank');                                 // -> ''   (nothing left once the markup is gone)
CkRequest::Html(key: 'blank', fallback: '<i>none</i>');   // -> 'none'   (the fallback is read the same way)
CkRequest::Html(key: 'bio', maxLength: 5);                // -> 'Hello'

Map

A nested bag — a JSON object inside the payload.

static function Map(
    ?string $key = null,
    bool $throwIfInvalid = CkSettings::DefaultThrowIfInvalid
): array
Parameter Type Default What it does
key ?string null The key, matched exactly; tried once more trimmed when the exact key is absent, and read as a dotted path through nested maps when it holds a dot.
throwIfInvalid bool CkSettings::DefaultThrowIfInvalid (false) Pass true to throw a CkRequestException that says why, instead of answering an empty array.

Returns

When Default With throwIfInvalid: true
The entry is a map The nested bag, ready for another reader The same
Rejected: missing entry, null, or not a map An empty array — never null Throws CkRequestException

CkDictionary::Map over the part's bag. A dotted key reaches inside without it: CkRequest::String('address.zip').

use SafeCoder\CommonKit\CkRequest;
use SafeCoder\CommonKit\CkUS;

$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$_POST = ['address' => ['zip' => '2134', 'city' => 'Boston']];

CkRequest::Map('address');                         // -> ['zip' => '2134', 'city' => 'Boston']
CkUS::Zip(CkRequest::String('address.zip'));       // -> '02134'   (the lost leading zero, back)
CkRequest::Map('city');                            // -> []

Maps

A list of nested bags — one per line of an order.

static function Maps(
    ?string $key = null,
    bool $throwIfInvalid = CkSettings::DefaultThrowIfInvalid
): array
Parameter Type Default What it does
key ?string null The key, matched exactly; tried once more trimmed when the exact key is absent, and read as a dotted path through nested maps when it holds a dot.
throwIfInvalid bool CkSettings::DefaultThrowIfInvalid (false) Pass true to throw a CkRequestException that says why, instead of answering an empty array.

Returns

When Default With throwIfInvalid: true
The entry is a list of maps The bags, one per item The same
The entry is one map A list of one The same
Rejected: missing entry, null, or items that are not maps An empty array — never null Throws CkRequestException

CkDictionary::Maps over the part's bag.

use SafeCoder\CommonKit\CkDictionary;
use SafeCoder\CommonKit\CkRequest;

$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$_POST = ['items' => [['sku' => 'A1', 'qty' => '2'], ['sku' => 'B2', 'qty' => '1']]];

$total = 0;
foreach (CkRequest::Maps('items') as $line) {
    $total += CkDictionary::Integer(dictionary: $line, key: 'qty');
}
echo $total;   // -> 3

MissingKeys

The required-fields check, in one call.

static function MissingKeys(
    mixed ...$keys
): array
Parameter Type Default What it does
...keys mixed — The keys that must be present, loose or as a list. An entry that is not text, or is blank, declares nothing.

Returns

When Result
Every key has an entry — a null value counts as present An empty array
Some do not Those keys, in the order given

Never throws. CkDictionary::MissingKeys over the part's bag.

use SafeCoder\CommonKit\CkRequest;

$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$_POST = ['name' => 'Ana', 'email' => 'ana@example.com', 'coupon' => ''];

CkRequest::MissingKeys('name', 'email');                        // -> []
CkRequest::MissingKeys('name', 'email', 'country', 'phone');    // -> ['country', 'phone']
CkRequest::MissingKeys(['name', 'email', 'country']);           // -> ['country']   (as a list, too)
CkRequest::MissingKeys('coupon');                               // -> []   (sent empty: present)

Number

A number, with the kind chosen at the end of the call.

static function Number(
    ?string $key = null
): CkRequestNumber
Parameter Type Default What it does
key ?string null The key, matched exactly; tried once more trimmed when the exact key is absent, and read as a dotted path through nested maps when it holds a dot.

Returns the entry as a CkRequestNumber, never null, with every CkNumber reader and its OrNull twin on it — each the reader of the same name over the entry found, with the same parameters after the input, the same rules and the same reasons:

On it Answers Parameters after the input
Integer(…) / IntegerOrNull(…) An INT column, four bytes — CkNumber::Integer unsigned, exact, fallback, throwIfInvalid / unsigned, exact, treatZeroAsMissing
TinyInt(…) / TinyIntOrNull(…) A TINYINT column, one byte — CkNumber::TinyInt the same
SmallInt(…) / SmallIntOrNull(…) A SMALLINT column, two bytes — CkNumber::SmallInt the same
MediumInt(…) / MediumIntOrNull(…) A MEDIUMINT column, three bytes — CkNumber::MediumInt the same
BigInt(…) / BigIntOrNull(…) A BIGINT column, eight bytes — CkNumber::BigInt the same
Float(…) / FloatOrNull(…) A float, about 15 significant digits — CkNumber::Float fallback, throwIfInvalid / treatZeroAsMissing
Decimal(…) / DecimalOrNull(…) Exact numeric text for a DECIMAL column — CkNumber::Decimal fallback, throwIfInvalid / treatZeroAsMissing

A missing entry is the fallback, or null from a twin, and so is a value that cannot be converted; a twin reads a zero as missing too unless treatZeroAsMissing: false, for columns where 0 stands in for "not set". The lookup is done once, here, and the object answers for that entry alone. The twins never throw; with throwIfInvalid: true a reader raises a CkRequestException naming the key, with the CkNumberException behind it. First the number, then what it does: there is no flat reader on this class, and no plain Int in the library, so every number is judged by the column that will hold it — Integer for an INT, SmallInt for a SMALLINT. An amount of money is not a number here: CkMoney::Amount, handed String, knows the currency.

use SafeCoder\CommonKit\CkRequest;

$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$_POST = ['id' => '7', 'qty' => '3.7', 'zero' => '0', 'price' => '19.99', 'neg' => '-5', 'big' => '9007199254740993'];

CkRequest::Number('id')->Integer();                            // -> 7   (an INT column, four bytes)
CkRequest::Number('id')->BigInt();                             // -> 7   (a BIGINT column, eight)
CkRequest::Number('qty')->SmallInt();                          // -> 4   ("3.7" rounds to nearest)
CkRequest::Number('qty')->SmallInt(exact: true);               // -> 0   (a fraction is refused: the fallback)
CkRequest::Number('qty')->SmallIntOrNull(exact: true);         // -> null   (the same, as null)
CkRequest::Number('neg')->Integer();                           // -> 0   (unsigned by default: a negative is refused)
CkRequest::Number('neg')->Integer(unsigned: false);            // -> -5
CkRequest::Number('big')->TinyInt();                           // -> 0   (out of range is the fallback, never a clamp)
CkRequest::Number('big')->BigInt();                            // -> 9007199254740993
CkRequest::Number('price')->Decimal();                         // -> '19.99'
CkRequest::Number('price')->FloatOrNull();                     // -> 19.99
CkRequest::Number('zero')->IntegerOrNull();                        // -> null   (a zero is missing to a twin)
CkRequest::Number('zero')->IntegerOrNull(treatZeroAsMissing: false);   // -> 0
CkRequest::Number('gone')->BigIntOrNull();                     // -> null
CkRequest::Number('gone')->BigInt(fallback: -1);               // -> -1

String

Clean text — what the domain readers take next.

static function String(
    ?string $key = null,
    mixed $fallback = null,
    bool $throwIfInvalid = CkSettings::DefaultThrowIfInvalid
): string
Parameter Type Default What it does
key ?string null The key, matched exactly; tried once more trimmed when the exact key is absent, and read as a dotted path through nested maps when it holds a dot.
fallback mixed null What comes back, read as text too, when the entry is missing or not text; an empty string when null.
throwIfInvalid bool CkSettings::DefaultThrowIfInvalid (false) Pass true to throw a CkRequestException that says why, instead of answering the fallback.

Returns

When Default With throwIfInvalid: true
The entry is text, a number or a boolean The clean text, as CkText::String writes it: trimmed, composed, invisible characters removed The same
Rejected: missing entry, null, or a value that is not text — a list, a map The fallback, read as text; '' when none Throws CkRequestException

CkDictionary::String over the part's bag. Cleaning happens here, and the kind of the value is judged next, by the reader that owns it.

use SafeCoder\CommonKit\CkEmail;
use SafeCoder\CommonKit\CkMoney;
use SafeCoder\CommonKit\CkRequest;
use SafeCoder\CommonKit\CkText;

$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$_POST = ['name' => '  ana  maria ', 'email' => 'ANA@EXAMPLE.COM', 'id' => '7', 'budget' => '$1,299.99'];

CkRequest::String('name');                         // -> 'ana maria'   (trimmed, the run of spaces inside collapsed)
CkText::Name(CkRequest::String('name'));           // -> 'Ana Maria'
CkEmail::Address(CkRequest::String('email'));      // -> 'ana@example.com'
CkRequest::String('id');                           // -> '7'
CkRequest::String(key: 'phone', fallback: 'n/a');  // -> 'n/a'
CkMoney::Amount(CkRequest::String('budget'));      // -> 1299.99   (money: the clean text, into the reader that knows the currency)

Uuid

An identifier, with the OrNull twin one word away.

static function Uuid(
    ?string $key = null
): CkRequestUuid
Parameter Type Default What it does
key ?string null The key, matched exactly; tried once more trimmed when the exact key is absent, and read as a dotted path through nested maps when it holds a dot.

Returns the entry as a CkRequestUuid, never null, with the two CkUuid readers on it — each the reader of the same name over the entry found:

On it Answers Missing entry Parameters after the input
ToUuid(…) The identifier, a ramsey/uuid object — CkUuid::ToUuid The nil UUID, never an invented one treatZerosAsMissing, throwIfInvalid
ToUuidOrNull(…) The same, or null for anything unusable — CkUuid::ToUuidOrNull null treatZerosAsMissing

First the identifier, then what it does: there is no flat ToUuid on this class; the twin is for a nullable column.

use SafeCoder\CommonKit\CkRequest;

$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$_POST = ['id' => '{2ED6657D-E927-568B-95E1-2665A8AEA6A2}', 'parent' => '', 'nil' => '00000000-0000-0000-0000-000000000000'];

CkRequest::Uuid('id')->ToUuid();                               // -> Uuid 2ed6657d-e927-568b-95e1-2665a8aea6a2
CkRequest::Uuid('parent')->ToUuidOrNull();                     // -> null   (sent empty: nothing to invent)
CkRequest::Uuid('parent')->ToUuid();                           // -> Uuid 00000000-0000-0000-0000-000000000000
CkRequest::Uuid('nil')->ToUuidOrNull();                        // -> null   (the all-zeros UUID is missing)
CkRequest::Uuid('nil')->ToUuidOrNull(treatZerosAsMissing: false);   // -> Uuid 00000000-0000-0000-0000-000000000000

Good to know

  • It is CkPayload's readers plus the library's own, read once. Every flat reader here answers what the CkDictionary reader of the same name answers over the request's own part — on a POST, Bag()->Body() — Html what CkText::Html answers for the entry, every method on a Number, Date or Uuid entry what CkNumber, CkDate or CkUuid answers for the value, and the request is read through the CkPayload reader its content type names — the same readers, the same cap, the same reasons. Nothing is decided twice.
  • First the thing, then what it does — and the twin one word away. A number, a date and an identifier are never read flat: CkRequest::Number('id')->Integer() is the everyday number, an INT column, and CkRequest::Number('qty')->SmallIntOrNull() chooses the column and asks for null instead of a fallback — for a nullable column, where a stand-in number would be a lie; CkRequest::Date('when')->Carbon() and CkRequest::Uuid('id')->ToUuid() are the same door for a date and an identifier. The lookup is done once, in Number, Date or Uuid, and the object answers for that entry alone.
  • Nothing to call first, and nothing to call after. The first member called reads the live request; every later call reads what was read. A request has one payload, and it does not change while it is being processed, so there is no load and no refresh.
  • The query string and the body never merge. $_REQUEST lets a query parameter override a body field; here the body is a POST's own part, and the address is asked for by name with Bag()->Query(). A form cannot be tampered with from the address.
  • The client address is the connection's. Bag()->ClientAddress() is REMOTE_ADDR read through CkIP::Address: canonical, or empty when none was reported or what was reported is not an address. A proxy's x-forwarded-for is a header in Bag()->Headers(); which proxies to believe is a decision about your deployment, so the library never makes it — CkIP::IsInternal and CkIP::InRanges are there when you do.
  • One request per request, on any runtime. The request as read carries a fingerprint of the live one — $_SERVER's request time, method, address, query string, content type and length. On a worker runtime the next request changes it, and the request is read again before anything is answered from it.
  • Under WordPress, CkSettings::RequestUnslash. WordPress puts a backslash before every quote in the superglobals; set the constant true in the plugin's copy of the library and one level is stripped from the query string, the multipart fields, the headers and the cookies as the request is read — never from the raw body, which no host touches.
  • The bag is raw on purpose, and nothing comes out raw. Values are text, as the browser sent them: cleaning happens at each read, where the kind of the field is known, and Explain on the owning class says why a value was refused. There is no Value reader here on purpose: a request value leaves through the reader that owns its kind, never as it came. For a reader this class has no twin of — CkMoney::Amount, CkGeo::Latitude, CkCountry::Alpha2 — hand it String, the clean text; an enum is CkDictionary::Enum over Bag()->Body(), and a list of values — tags, ids — CkDictionary::Strings or CkDictionary::Items over the same bag, which is all a twin here would do.
  • Building blocks, not a second CkDictionary. This class reads the kinds every form carries and hands back the bags a nested value comes as — Map, Maps, the whole part through Bag() — for CkDictionary to read on with your own logic; it does not repeat what CkPayload and CkDictionary already do.
  • A multipart body is $_POST. PHP has consumed it before any code runs, so on POST the body part is $_POST, keys as PHP rewrote them. On any other method a multipart body is not read. The declared body length and combined length of all field names and text values, including nested array keys and values before unslashing, must fit CkSettings::MaxPayloadInputLength. Lengths count UTF-16 units, so an emoji counts as two. Raw request bodies are read under a finite bound and their actual length, including padding, is checked even when Content-Length is absent or too small.
  • A test sets the request as the web server would. $_SERVER['REQUEST_METHOD'], $_SERVER['CONTENT_TYPE'], $_SERVER['QUERY_STRING'] and $_POST, then the handler runs and the first call reads them, as every example on this page shows. A JSON body cannot be faked that way — php://input is not writable — and is read by one private step that takes the pieces, which the library's own tests hand in; a test of your handler hands it a form.

If you want errors instead

Pass throwIfInvalid: true and a reader whose entry is missing or cannot be converted raises a CkRequestException (from SafeCoder\CommonKit\Exceptions) instead of answering its fallback; the exception carries the Key, the Input and the Reason in the words the owning reader uses, with that reader's own exception behind it as the previous one — a CkDictionaryException, a CkNumberException, a CkDateException or a CkUuidException. A request that cannot be read is no error by itself — Explain says why — except under CkSettings::DefaultThrowIfInvalid, the development switch, when the first read throws too, with Key null: CkRequest: cannot read the request — no body.

use SafeCoder\CommonKit\CkRequest;

$_SERVER['REQUEST_METHOD'] = 'POST';
$_SERVER['CONTENT_TYPE'] = 'multipart/form-data';
$_POST = ['qty' => '3.7'];

CkRequest::Number('qty')->Integer(exact: true, throwIfInvalid: true);   // throws CkRequestException
CkRequest::String(key: 'email', throwIfInvalid: true);               // throws CkRequestException