Monday, 8 September 2014

PHP: how does PHP deal with same-named form values? [shudder]

G'day:
I seriously hope I'm wrong here, but it seems to me that PHP's default "handling" of multiple same-named form fields is... to ignore all bar the last one. That's not to say the values aren't still accessible for one to deal with via bespoke code, but... come on PHP. Really?

Here's a repro:

<?php
// formBasic.php

$colours = array("whero", "karaka", "kowhai", "kakariki", "kikorangi", "tawatawa", "mawhero");
$numbers = array("tahi","rua","toru","wha","rima","ono","whitu","waru","iwa","tekau");
?>

<form method="post" action="dumpPost.php">
    Colours:<br>
    <?php foreach($colours as $colour){ ?>
        <input type="checkbox" name="colour" value="<?= $colour ?>"><?= $colour ?><br>
    <?php } ?>
    <hr>Number:<br>
    <?php foreach($numbers as $number){ ?>
        <input type="checkbox" name="number" value="<?= $number ?>"><?= $number ?><br>
    <?php } ?>
    <input type="submit" name="btnSubmit" value="Submit">
</form>

Here I have an array of colours and numbers, and make a series of checkboxes which one can - obviously - select zero or more of:


On submission, with CFML we'd get the familiar form scope here:


Or if we had this.sameFormDieldsAsArray=true set in Application.cfc, we'd get this:


(which is how it always should have bloody been, but that's another story).

Anyway, with PHP we get this:


I'm using dBug to emulate <cfdump> with PHP here. Initially I thought this was a bug in dBug, but no... it's how the $_POST variable works. In that, basically, it doesn't. This is a fundamental cock-up in PHP I reckon, because it simply is not representing the post data correctly.

One can work around this in a coupla ways. The easiest is to name the HTML controls with array notation name="colour[]". But I really think that's pollution. The mark-up shouldn't need to contend with the shortcomings of the back-end processing.

The other way is to DIY the unpacking of the post data. This is incredibly jerry-built, but sits better with me than messing with the HTML. This is my proof-of-concept function which deals with it, resulting in the same things CFML would offer if this.sameFormDieldsAsArray=true

// dumpPost.php

function unpackPost(){
    $post = array();
    foreach (explode('&', file_get_contents('php://input')) as $keyValuePair) {
        list($key, $value) = explode('=', $keyValuePair);

        if (!array_key_exists($key, $post)){
            $post[$key] = $value;
            continue;
        }

        if (is_array($post[$key])){
            $post[$key][] = $value;
            continue;
        }

        $temp = $post[$key];
        $post[$key] = array($temp, $value);

    }
    return $post;
}

$post = unpackPost();
new dBug($post);

(I've linked to the stuff I needed to look-up in the docs, as they'd be new things I've mentioned on this blog).

Notes:

  • php://input is where PHP sticks the raw post data
  • explode() converts a string to an array, using a substring as the delimiter. Kinda analogous to listToArray(), except the delimiter can be more than one character. So like a senisble implementation of listToArray().
The logic here is that I loop through each name/value pair in the post data, and append the name/value to the $post variable according to one of three rules:

  1. Is the key is not already in the array, just add it in as a simple value;
  2. If it's already there, and its value is an array, then append the current key/value value to said array;
  3. lastly it's the case that previously (1) was fulfilled - so the key's value is currently the first simply value - but now we have a second value so we need to convert the key's value to an array, containing the initial first value and the new value.
And that outputs like this:


So that's fine.

But, really? Should we need to jump through hoops like this? Did the person writing the implementation of the code which populates $_POST not stop to think that... it doesn't work? At no point in PHP's history did anyone go "shit... better fix that?"

Hopefully some PHP-savvy person will happen upon this and explain to me why I've got the wrong end of the stick. I did spend some time going "no... nonono... this can't be right?" whilst googling and stackoverflowing. Indeed the basis for the UDF above was gleaned from Stack Overflow (although I thought their implementation was a bit leaden, so changed it a bit).

Sigh.

Back to my studies...

--
Adam