12/28/2015

Checking whether a form was posted, in PHP

In plain PHP, test the request method rather than the presence of a field:

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // handle the submission
}

In Symfony, or anything else built on HttpFoundation, ask the request:

if ($request->isMethod('POST')) {
    // handle the submission
}

Use the framework's version wherever you have one. It is not merely tidier: $_SERVER is the raw value, and the request object accounts for the things that change it — a trusted proxy, and the _method override some clients use to send PUT or DELETE through an HTML form, which only supports GET and POST.

Why not test for a field

The common version of this check is:

if (isset($_POST['submit'])) {   // don't

It fails in two ordinary situations.

A form submitted by pressing Enter may not send the submit button at all. The button is only included when it is the thing that was activated, so a keyboard submission from a text field can arrive without it.

An unticked checkbox is not sent. This is the one that costs an afternoon. HTML posts nothing for a checkbox that is not checked — no empty value, no key — so a settings form where the user turned everything off arrives looking like it was never submitted. Any code shaped like if (isset($_POST['notify'])) cannot tell "unticked" from "not submitted", which means it can never turn a setting off.

Testing the method separates the two questions properly: was this a submission is REQUEST_METHOD, and what did they choose is read afterwards, with a default:

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $notify = isset($_POST['notify']);   // false is a real answer here
}

Use === rather than ==

== is loose comparison. It is unlikely to bite on this particular string, but there is no reason to accept the risk when the strict operator is the same length.

Questions this keeps raising

Why does my checkbox never turn off?

Because an unchecked checkbox is not submitted at all - HTML sends no key for it. Code that reads isset($_POST['field']) to decide whether the form was posted cannot distinguish "unticked" from "not submitted", so it never sees the off state. Test REQUEST_METHOD for the submission and read the checkbox separately.

Should I use $_SERVER or the framework's request object?

The framework's, wherever you have one. $_SERVER['REQUEST_METHOD'] is the raw value; Symfony's $request->isMethod() accounts for trusted proxies and for the _method override used to send PUT or DELETE from an HTML form, which natively supports only GET and POST.

Is $_SERVER['REQUEST_METHOD'] safe to trust?

For deciding whether to process a submission, yes - it comes from the request line rather than from a header a client can invent freely. It is not an authorization check, though. Knowing the request was a POST says nothing about who sent it, which is what a CSRF token is for.

What about checking the referer instead?

Do not. The Referer header is optional, routinely stripped by browsers, proxies and privacy settings, and trivially forged. It cannot tell you a form was submitted and it cannot tell you where from.

Filed under