Passing arrays

Modification or insertion of records into a database via web user interface (i.e. your application) is usually performed one by one. User opens the new record form or record details page (user is redirected to some detail page – ID of record is passed as URL parameter) and fills up information using generated form. The form is then submitted and backend inserts or updates related records in a database. Similar process is used to delete records – user clicks on a delete button and the backend deletes exactly one record according to passed ID value. Sometimes the designer of user interface wants to make this process a bit less verbose.

It can be more convenient to create/modify/delete multiple database records at once. To achieve this you can simply generate a form using foreach loop. This will result into a form with many input fields which represent same type of information for different records. The question is how to pass data back to the backend and maintain information about which piece of data belongs where.

The key lies in form inputs’ name attribute. We can pass arrays instead of scalar values using a specific notation in name attribute value:

<form action="..." method="post">
    <label>Person 1</label>
    <input name="birthday[]">
    <label>Person 2</label>
    <input name="birthday[]">
    <label>Person 3</label>
    <input name="birthday[]">
    <input type="submit">
</form>

After submission of such form, the backend will have access to birthday (not birthday[]!) key in POST data which will hold array of length 3 indexed from zero, i.e. in $_POST['birthday'][0] will be content of first input and so on. There can be more inputs with different name attribute base. If you design your form carefully, you can be sure that the order of all submitted inputs will match in all arrays.

You can also set a concrete key in name attribute:

<form action="..." method="post">
    <label>Person 1</label>
    <input name="birthday[59]">
    <label>Person 2</label>
    <input name="birthday[12]">
    <label>Person 3</label>
    <input name="birthday[80]">
    <input type="submit">
</form>

This is useful when you want to modify existing information – the key may represent ID of record in a database table.

The keys in name attribute does not have any quotes around and it can contain a string too.

You can use multiple brackets to nest deeper in POST data array, e.g. name="attr[123][email][]" will be accessible as $_POST[123]['email'][0].

How does browser transfer these information and how does it become a multidimensional array?

The browser does not care very much about contents of name attributes – remember that contents of name attribute is generated on backend using templating engine. It simply composes a string which consists of name–value pairs (name[1]=value1&name[2]=value2&...) and sends it to the backend either in URL using GET method or in payload of request using POST. Only inputs which are not disabled and have name attribute are considered for this operation. The browser does it even if some input has exactly same name value as another input (name=value1&name=value2&...) – in this case you will find only the latter value in $_GET/$_POST because there are no brackets.

The hard work is carried out by PHP interpreter which detects squared brackets in the names of input parameters and composes a multidimensional array (or rewrites previous value when there are no squared brackets).

It is very important to place squared brackets into name attribute when you want to pass multiple values. The browser or PHP interpreter does not generate any error or warning when multiple input fields have same value inside their name attribute.

Task – make your own form with multiple fields

Let’s say that you want to modify height of multiple persons at once (suppose that the user of your application can keep records about some kids which have grown up since last update). The form has to contain input fields for each person and those fields should be prefilled with known values.

Prepare a template which generates a large form based on selection of all persons from a database. Remember to set name attribute of input and pass ID of person into squared brackets without quotes. This is a bit tricky because PHP uses squared brackets to access array elements – name="height[{$person['id_person']}]".

Template height-form.latte:

{extends layout.latte}

{block title}Height form{/block}

{block body}
<form method="post">
    {foreach $persons as $person}
    <label>{$person['first_name']} {$person['last_name']}</label>
    <input type="number" name="height[{$person['id_person']}]" value="{$person['height']}">
    <br>
    {/foreach}
    <input type="submit" name="save" value="Update">
</form>
{/block}

Create also a GET route which loads all young persons or persons with unknown height.

Route in routes.php:

<?php
$app->get('/height-form', function(Request $request, Response $response, $args) {
    try {
        //select persons from database (young or with unknown height)
        $stmt = $this->db->query('
            SELECT * FROM person
            WHERE
              birth_day >= NOW() - INTERVAL \'20\' YEAR
              OR height IS NULL
            ORDER BY last_name, first_name
        ');
        $stmt->execute();
    } catch (PDOException $e) {
        $this->logger->error($e->getMessage());
        exit($e->getMessage());
    }
    $tplVars['pageTitle'] = 'Persons List';
    $tplVars['persons'] = $stmt->fetchAll();
    $this->view->render($response, 'height-form.latte', $tplVars);
})->setName('heightForm');;

Now you should be able to see all submitted values and keys representing IDs of persons using print_r($_POST) or similar command. Then you can update person records in the database using UPDATE SQL statement. The ID of a person should be accessible as the key of array. Use foreach($array as $key => $value) { ... } to get the ID.

Route in routes.php:

<?php
$app->get('/height-form', function(Request $request, Response $response, $args) {
    //...
})->setName('heightForm');

$app->post('/height-form', function(Request $request, Response $response, $args) {
    $data = $request->getParsedBody();
    try {
        //update individual records
        foreach($data['height'] as $id => $h) {
            $stmt = $this->db->prepare('UPDATE person SET height = :h WHERE id_person = :id');
            //store null or actual value
            $stmt->bindValue(':h', !empty($h) ? intval($h) : null);
            $stmt->bindValue(':id', $id);
            $stmt->execute();
        }
        //redirect back to form
        return $response->withHeader('Location', $this->router->pathFor('heightForm'));
    } catch (PDOException $e) {
        $this->logger->error($e->getMessage());
        exit($e->getMessage());
    }
});

After you finish reading of JavaScript article, return here and try to create a simple reset button next to each input field which will simply return original value into the field.

Template height-form.latte:

{extends layout.latte}

{block title}Height form{/block}

{block body}
<form method="post" name="heightForm">
    {foreach $persons as $person}
        <label>{$person['first_name']} {$person['last_name']}</label>
        <input type="number" name="height[{$person['id_person']}]" value="{$person['height']}">
        <button type="button" onclick="resetHeight({$person['id_person']}, {$person['height']})">
            Reset
        </button>
        <br>
    {/foreach}
    <input type="submit" name="save" value="Update">
</form>
<script type="text/javascript">
    function resetHeight(personId, origValue) {
        var form = document.forms['heightForm'];
        form['height[' + personId + ']'].value = origValue;
    }
</script>
{/block}

Did you notice that for persons with unknown height (NULL value in database) the Latte engine produces different output in JavaScript and HTML context? Compare this: onclick="resetHeight(47, null)" and <input type="number" name="height[47]" value="">. In JavaScript context (onclick) the {$person['height']} Latte command produces null and for HTML context (value="...") it produces just empty string. This cool feature is called “context aware escaping”.

In JavaScript, a call to function onclick="resetHeight(47, )" would obviously cause a syntax error. Therefore the Latte templating engine handles the output of a special NULL value differently - this is a feature of Latte and it is not common among templating engines.

Multiple values from single input

I demonstrated this functionality so far using a single input for each value. There is an exception: <select> element with multiple attribute needs to have squared brackets too because it generates a query parameter for each selected option:

<form action="..." method="post">
    <select name="multi[]" multiple="multiple">
        <option value="1">One</option>
        <option value="2">Two</option>
        <option value="3">Three</option>
    </select>
    <input type="submit">
</form>

It works exactly the same as with multiple input fields – imagine multi-select as a set of checkboxes and each of them having same name attribute value.

In this case you cannot specify a key inside the squared brackets. Use another dimension of array to overcome this restriction name="multi[123][]".

Summary

You probably see that you can create useful web applications even without such complicated gadgets. On the other hand, useful and easy to use user interface can make difference and give you a competitive advantage. The decision is up to you.

Introduced approach may be widely extended with JavaScript and/or AJAX. We can easily generate form input fields on demand and even prefill some values from backend.

A useful improvement in your project may be a form which will allow to add or modify multiple contact information of a person at once (remember to include dropdown with contact type selection). This is also a small challenge because it involves cloning of HTML elements using JavaScript.

New Concepts and Terms

  • passing arrays

Control question

  • Is there any limit for GET or POST request/URL size?
  • Is radio button with no selected option OK?