PHP populates GET, POST, COOKIE, and FILES arrays before application code runs, but their values remain caller-controlled. A field expected to be a string may arrive as an array by using bracketed form names. A missing field, empty field, and malformed field need distinct decisions. Validate method, content type, and scalar shape at the edge, then convert to a small command object. Never use an input array wholesale in database writes or templates. Output encoding is a separate boundary: HTML text, HTML attributes, URLs, JSON, and SQL each have different handling rules. A single generic sanitizer cannot replace typed input and context-specific output.
PHP Superglobal Input and Output Trust
Working case
An inspector submits permitId=447 and a revision number through a form. A forged request sends permitId[]=447; code that assumes a scalar emits a warning or turns an array into an unexpected value. Another payload includes markup in a note field that is valid text but dangerous if inserted into HTML without escaping. The correct handler rejects the array-shaped ID, accepts a bounded note as data, and uses HTML escaping only when rendering the note. A value used in a database query is bound as a parameter, not escaped with HTML rules. The audit record stores the original permitted note, not the display-escaped version.
Implementation boundary
<?php
function permitIdFromPost(array $post): ?int {
if (!isset($post['permitId']) || !is_string($post['permitId'])) return null;
$permitId = filter_var($post['permitId'], FILTER_VALIDATE_INT);
return is_int($permitId) && $permitId > 0 ? $permitId : null;
}
echo permitIdFromPost(['permitId' => '447']) . PHP_EOL;Start with an explicit route contract: POST only, known content type, maximum field lengths, and typed required fields. Distinguish `isset` behavior from presence with an empty value, and inspect `is_string` before calling string functions. Use integer parsing with range checks rather than loose comparisons. Build a typed associative command from accepted fields, ignoring or rejecting unexpected fields based on the endpoint contract. On output, use escaping for the exact context; `htmlspecialchars` is appropriate for HTML text or quoted attributes with the right flags and encoding, but it does not make a URL safe or parameterize SQL. Keep CSRF verification, authentication, and object authorization as separate checks.
Cost and boundaries
A bounded request with F fields and B total bytes takes O(B) work to validate strings and construct the command. Unbounded arrays or repeated keys can create more parser and application work before this handler runs, so configure web server and PHP input limits as well. Escaping at render time costs O(L) for an L-character value and may expand its byte length. Do not pre-escape persisted data: it causes double encoding and makes the same record unusable in JSON or email. Track malformed shapes, field rejection rates, and output context failures without logging confidential form content. Tests should include every supported content type and nested-field shape.
Failure trace
Send a missing permitId, an empty string, a negative ID, a bracketed array, and a string with trailing letters. Confirm none become permit 447 by loose coercion. Submit a note containing ampersands, quotes, and markup, then render it in HTML text and in a JSON response; each must preserve the data while using its own encoding. Feed a URL field with an unexpected scheme and show that HTML escaping alone does not approve it. Replay the form without a CSRF token and as a user who lacks access to permit 447. Neither case should reach the write transaction.
Verification
- Array-shaped fields never become scalar domain IDs.
- HTML and JSON output preserve the same data safely.
- CSRF and record authorization run before writes.
Practice drill
Build a POST-only permit note handler that admits ID 447, revision 8, and a note of at most 720 characters. Reject array-shaped scalar fields, extra-long text, and invalid revision ranges with field-specific errors. Create a command array containing only these three fields. Render the saved note in an HTML page with the correct encoding and return it separately as JSON using the JSON encoder. Add tests for nested form names, malformed UTF-8 policy, a missing CSRF token, and a valid user who lacks record-level permission. Keep the validation result independent of the template implementation.
Decision note
Request arrays are untrusted transport data; domain commands and rendered outputs are separate representations.
Common Mistakes
- Assuming a form field is always a string.
- Persisting HTML-escaped text as the canonical note.
- Using HTML escaping as SQL or URL validation.
Related lessons
PHP Request, Session, and Persistence Boundaries; PHP Session Rotation, Locking, and Logout; PHP PDO Transactions, Replay, and Query Identity; PHP Upload Tempfiles, Private Storage, and Lifecycle; HTTP requests: keep method, status, and body contracts separate; Sessions and CSRF: keep identity on the server; Authorization: check permission for this record on every request.
Apply and check
Build Project: PHP Permit Review Intake and review Web Development: PHP Request Contracts.
