array_intersect_key: The Input Whitelist Hiding in the Standard Library
The bug class is old enough to drink: a form handler does $user->update($_POST) or builds an UPDATE from request keys, and someone adds role=admin to the POST body with DevTools. Mass assignment. The fix everyone knows is "whitelist your fields" - the fix nobody remembers is that PHP has a one-liner for it:
$allowed = ['name', 'email', 'bio', 'website'];
$data = array_intersect_key($_POST, array_flip($allowed));
// Only whitelisted keys survive. role=admin? Gone, silently.
array_intersect_key keeps entries from the first array whose keys exist in the second; array_flip turns your list of allowed names into the key-shaped comparison array it wants. Ten seconds to write, trivially auditable in review - the entire filter is visible in the $allowed literal.
The complement: array_diff_key for stripping
// Blocklist shape - remove what must never pass through
$safe = array_diff_key($row, array_flip(['password_hash', 'remember_token', 'totp_secret']));
echo json_encode($safe);
Same trick inverted: keep keys not in the second array. Right for API responses where you're redacting known-sensitive columns from otherwise-fine rows. (General rule stands: whitelists for input, because you know what you accept; blocklists only for output redaction, where the row shape is yours.)
Building the full input pipeline
$defaults = ['bio' => '', 'website' => null];
$data = array_intersect_key($_POST, array_flip($allowed)) // 1. whitelist
+ $defaults; // 2. fill gaps (union keeps left)
$data = array_map(fn ($v) => is_string($v) ? trim($v) : $v, $data); // 3. normalize
// 4. validate values - whitelisting KEYS says nothing about VALUES
$errors = validate($data, [
'email' => fn ($v) => filter_var($v, FILTER_VALIDATE_EMAIL),
'website' => fn ($v) => $v === null || filter_var($v, FILTER_VALIDATE_URL),
]);
Step 4 is the honesty clause: intersect_key controls which fields exist, not what's in them. Key filtering plus value validation together are what frameworks' validated() helpers do - and this is exactly what to write when you're in vanilla PHP, a webhook endpoint, or a WordPress handler where the framework isn't holding your hand.
Related key-fu worth knowing
array_key_first()/array_key_last()- no more reset()/end() pointer dances.array_is_list($arr)(8.1) - distinguishes ['a','b'] from ['x'=>'a'] before you JSON-encode something that must be an array, not an object.array_filter($arr, $fn, ARRAY_FILTER_USE_KEY)- predicate-based key filtering when the whitelist is a pattern (e.g., keys starting with 'meta_') rather than a list.
Every framework wraps this in something (Laravel's $request->only()); knowing the primitive means you can build the safe path anywhere - and recognize its absence in code review, which is where this one-liner earns its keep.
Full-stack web developer sharing practical tutorials and building tools that ship.
Got something on your mind?
My inbox is open - no forms disappearing into the void here.
- Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
- Hire me for a project Have something custom in mind? Let's talk scope and timelines.
- Product support Bought something here? I'll help you get it running.
I usually reply within 1-2 business days.