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 & 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 whatCkText::Htmlanswers 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, anINTcolumn, andCkRequest::Number('qty')->SmallIntOrNull()chooses the column and asks fornullinstead of a fallback — for a nullable column, where a stand-in number would be a lie;CkRequest::Date('when')->Carbon()andCkRequest::Uuid('id')->ToUuid()are the same door for a date and an identifier. The lookup is done once, inNumber,DateorUuid, 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.
$_REQUESTlets a query parameter override a body field; here the body is a POST's own part, and the address is asked for by name withBag()->Query(). A form cannot be tampered with from the address. - The client address is the connection's.
Bag()->ClientAddress()isREMOTE_ADDRread throughCkIP::Address: canonical, or empty when none was reported or what was reported is not an address. A proxy'sx-forwarded-foris a header inBag()->Headers(); which proxies to believe is a decision about your deployment, so the library never makes it —CkIP::IsInternalandCkIP::InRangesare 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 constanttruein 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
Explainon the owning class says why a value was refused. There is noValuereader 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 isCkDictionary::EnumoverBag()->Body(), and a list of values — tags, ids —CkDictionary::StringsorCkDictionary::Itemsover 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 fitCkSettings::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 whenContent-Lengthis 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://inputis 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