Blog

Parsing Bracketless Multiple Select Inputs in PHP

One of the really anoying things about PHP is the need for every multiple select field to end the name in brackets. For example...

<select name="field">
    <option value="apple">Apple</option>
    <option value="microsoft">Microsoft</option>
    <option value="google">Google</option>
</select>

The name attribute would need to be field[] not just field.

The reason this is needed is because of the way PHP builds the $_GET and $_POST arrays.

You see, when a multiple select field is given multiple values e.g. apple and microsoft, two key value pairs are sent in the requset.

A GET request would create a query string that looks like this...

?field=apple&field=microsoft

Notice that field is included twice once for each value.

If the brackets were included the request would instead look like this.

?field%5B%5D=apple&field%5B%5D=microsoft

That is a pretty ugly URL. But that is what PHP requies of us. Or is it?

PHP uses the ending [] to determin that the field sends multiple values. Because of that instead of creating a string value for the field an array is created. This is true even if the field only sends one value.

// URL ?field%5B%5D=apple&field%5B%5D=microsoft

echo '<pre>' . print_r( $_GET, 1 ) . '</pre>';

/* The output
Array
(
    [field] => Array
    (
        [0] => apple
        [1] => microsoft
    )
)
*/

However, if we sent only one key value pair, The result would still be an array.

// URL ?field%5B%5D=apple

echo '<pre>' . print_r( $_GET, 1 ) . '</pre>';

/* The output
Array
(
    [field] => Array
    (
        [0] => apple
    )
)
*/

But if we removed the [] a.k.a %5B%5D at the end, The result for field would be a string value.

// URL ?field=apple

echo '<pre>' . print_r( $_GET, 1 ) . '</pre>';

/* The output
Array
(
    [field] => apple
)
*/

This is important. If we send a two or more values without the brackets each value is over written by the following value. This will cause problems because only the last value is given.

// URL ?field=apple&field=microsoft

echo '<pre>' . print_r( $_GET, 1 ) . '</pre>';

/* The output
Array
(
    [field] => microsoft
)
*/

Is there a way to avoid this URL monstrocity PHP is trying to for on us? Yes, there is.

Bracketless Query String

The way to get around this is to parse the query string itself. Unfortunately, all the standard methods to do this in PHP will overwrite repeated keys with the last value if [] is not appended to the key.

The following function will parse the query string and generate an associative array of form values.

/**
 * Parses GET and POST form input like $_GET and $_POST, but without requiring multiple select inputs to end the name
 * in a pair of brackets.
 * 
 * @param  string $method      The input method to use 'GET' or 'POST'.
 * @param  string $querystring A custom form input in the query string format.
 * @return array  $output      Returns an array containing the input keys and values.
 */
function bracketless_input( $method, $querystring=null ) {
    // Create empty array to 
    $output = array();
    // Get query string from function call
    if( $querystring !== null ) {
        $query = $querystring;
    // Get raw POST data
    } elseif ($method == 'POST') {
        $query = file_get_contents('php://input');
    // Get raw GET data
    } elseif ($method == 'GET') {
        $query = $_SERVER['QUERY_STRING'];
    }
    // Separerate each parameter into key value pairs
    foreach( explode( '&', $query ) as $params ) {
        $parts = explode( '=', $params );
        // Remove any existing brackets and clean up key and value
        $parts[0] = trim(preg_replace( '(\%5B|\%5D|[\[\]])', '', $parts[0] ) );
        $parts[0] = preg_replace( '([^0-9a-zA-Z])', '_', urldecode($parts[0]) );
        $parts[1] = urldecode($parts[1]);
        // Create new key in $output array if param does not exist.
        if( !key_exists( $parts[0], $output ) ) {
            $output[$parts[0]] = $parts[1];
        // Add param to array if param key already exists in $output
        } elseif( is_array( $output[$parts[0]] ) ) {
            array_push( $output[$parts[0]], $parts[1] );
        // Otherwise turn $output param into array and append current param
        } else {
            $output[$parts[0]] = array( $output[$parts[0]], $parts[1] );
        }
    }
    return $output;
}

Let's see the results if we use this function. We will return the GET data array from the following URLs using our new function.

// URL ?field=apple&field=microsoft

$get = bracketless_input('GET');
echo '<pre>' . print_r( $get, 1 ) . '</pre>';

/* The output
Array
(
    [field] => Array
    (
        [0] => apple
        [1] => microsoft
    )
)
*/

This works with brackets as well.

// URL ?field%5B%5D=apple&field%5B%5D=microsoft

$get = bracketless_input('GET');
echo '<pre>' . print_r( $get, 1 ) . '</pre>';

/* The output
Array
(
    [field] => Array
    (
        [0] => apple
        [1] => microsoft
    )
)
*/

It should be noted that if only one key / value pair is sent to the server, the result will be a string not an array like you would get if you used $_GET.

// URL ?field=apple

$get = bracketless_get();
echo '<pre>' . print_r( $get, 1 ) . '</pre>';

/* The output
Array
(
    [field] => apple
)
*/

Warning:

This does not work with enctype="multipart/form-data".

If your form requires multipart/form-data, you will have to use the brackets at the end of the name.

Daniel Morell

Daniel Morell

I am a fullstack web developer with a passion for clean code, efficient systems, tests, and most importantly making a difference for good. I am a perfectionist. That means I love all the nitty-gritty details.

I live in Wisconsin's Fox Valley with my beautiful wife Emily.

Daniel Morell

I am a fullstack web developer, SEO, and builder of things (mostly digital).

I started with just HTML and CSS, and now I mostly work with Python, PHP, JS, and Golang. The web has a lot of problems both technically and socially. I'm here fighting to make it a better place.

© 2018 Daniel Morell.
+ Daniel + = this website.