List-o-mania

The list() language construct is one of the most powerful constructs in PHP, allowing you to assign one or more elements from an array to specific named variables in a single step; but it may not work in quite the way that you would expect.

Let’s start with a simple example, a basic array of values, and we’ll use list() to extract the first two values from that array; I’m using short list syntax here for brevity (and because I feel that it’s cleaner), but the functionality is identical:


$data = ['A', 'B', 'C', 'D', 'E'];

[$firstValue, $secondValue] = $data;

var_dump($firstValue, $secondValue);

which gives us


string(1) "A"
string(1) "B"

and that’s the behaviour we would expect. Using list() has extracted the first and second elements from our array into the variables $firstValue and $secondValue… or has it?

That simple example has achieved the result that we wanted, but may not have done so in the way we expected. Does that actually matter? A short while ago, I ran a poll on twitter to see how many developers understand how list() actually works:

TwitterListPoll

Fully two thirds of developers that responded to the poll admitted that they didn’t get the correct answer to the example code that I’d posted. To recreate the example that I used for that poll, let’s reverse our array by sorting it, and see what we get this time:


$data = ['A', 'B', 'C', 'D', 'E'];
arsort($data);

[$firstValue, $secondValue] = $data;

var_dump($firstValue, $secondValue);

which gives us


string(1) "A"
string(1) "B"

?!? Dafuq!

You could be forgiven if you were expecting


string(1) "E"
string(1) "D"

but the thing about list() is that it works from the array keys, not from the position in the array; and when we reverse sorted our array, we used arsort() to preserve the keys. The position of the element with value A is now at the end of the array, but its index is still 0.


array(5) {
  [4]=>
  string(1) "E"
  [3]=>
  string(1) "D"
  [2]=>
  string(1) "C"
  [1]=>
  string(1) "B"
  [0]=>
  string(1) "A"
}

In using list() to extract values to variables with the names $firstValue and $secondValue, we weren’t being completely clear; we should probably have renamed those two variables as $valueAtIndex0 and $valueAtIndex1, because that is what we are actually doing.

In most cases this doesn’t make any difference; but that common misunderstanding about how list()` works can lead to some very difficult bugs when the array isn’t quite what we expect.


But the simple fact that list() works with keys and not with position in the array also gives us a lot more control and flexibility when we work with it. We can actually specify the keys that we want to extract in the list syntax.

If we wanted the second and third elements (the elements at offset 1 and offset 2), we could use an empty argument telling list() to skip the first element (the element at offset 0), and this is the approach that I see used most frequently:


[, $secondValue, $thirdValue] = $data;

var_dump($secondValue, $thirdValue);

but we could also do:


[1 => $secondValue, 2 => $thirdValue] = $data;

var_dump($secondValue, $thirdValue);

While the default behaviour of list() assumes an enumerated array, starting with index 0 and incrementing keys by 1 with no gaps, we can override that behaviour by specifying the indexes that we want to extract.

Suppose that we wanted to extract the first and last values from that array, we can actually specify which keys we want to get, ignoring any intermediate entries:


$data = ['A', 'B', 'C', 'D', 'E'];

[0 => $firstValue, 4 => $lastValue] = $data;

var_dump($firstValue, $lastValue);

and if we didn’t know the length of our array, then we can use an expression to determine the key:


$data = ['A', 'B', 'C', 'D', 'E'];

[0 => $firstValue, count($data)-1 => $lastValue] = $data;

var_dump($firstValue, $lastValue);

although (since PHP 7.3) using the array_key_first() and array_key_last() may be a better option than relying on your array being a strict enumerated list.


$data = ['A', 'B', 'C', 'D', 'E'];

[array_key_first($data) => $firstValue, array_key_last($data) => $lastValue] = $data;

var_dump($firstValue, $lastValue);

Note that since PHP 8.1, the array_is_list() function provides an easy way of checking if an array is enumerated, with keys beginning with 0 and no gaps, although it will return false if the keys aren’t in order.

Note also that array_key_first() and array_key_last() are based on the ordering of the array, not the lowest and highest index values.

Or we can use list() with associative arrays, where array_key_first() and array_key_last() can be even more useful because those functions work with both enumerated and associative arrays:


$data = ['A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5];

['A' => $firstValue, 'E' => $lastValue] = $data;

var_dump($firstValue, $lastValue);

Where list is particularly useful is that we can assign these values not only to variables, but to class properties, or even elements in a new array.


$data = range('A', 'Z');

$newArray = [];
[15 => $newArray[], 7 => $newArray[], 15 => $newArray[]] = $data;

var_dump(implode($newArray));

While my sample code above is fairly trivial, the ability to assign values from one array to elements in another array does give us an alternative approach for transforming data from one structure to another.


$data = [
    'id' => 123,
    'name' => 'Mark',
    'email' => 'mark@demon-angel.eu',
    'firstName' => 'Mark',
    'surname' => 'Baker'
];

$userData = [];
[
    'id' => $userData['id'],
    'name' => $userData['userName'],
    'email' => $userData['emailAddress'],
    'firstName' => $userData['foreName'],
    'surname' => $userData['familyName']
] = $data;

var_dump($userData);

Nor are we limited to “flat” arrays: in the same way that we can nest arrays, we can also nest list() syntax:


$data = [1, 2, [3, 4], 5];

[$a, $b, [$c, $d], $e] = $data;

var_dump($a, $b, $c, $d, $e);

So hopefully these few examples have demonstrated just how flexible and powerful the list() construct is in PHP, once we recognise that we’re working with keys and not with position in the array.


What you need to watch out for is that the array contains all the indexes that your list() expects: if you request the value at index 5, and there is no entry for index 5, then you’ll get a warning, and a null value will be assigned to that variable.


$data = [0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E'];

[0 => $firstValue, 5 => $lastValue] = $data;

Nor can you mix keyed and unkeyed entries in the list:


[$firstValue, 5 => $lastValue] = $data;

will result in a fatal error because the list entry for $firstValue is unkeyed, and the entry for $lastValue is keyed.


So knowing that list() works with array keys and not positions, and that we can specify the keys that we want to extract (even using functions and expressions to identify those keys) opens up a range of new possibilities in how we can use it, and what we can use it for.

There are still limitations with list() though, and I’ve addressed some of those in a separate series of posts.

These are all proposals for changes to list() in PHP Core that I would like to see introduced in PHP 8.3.

This entry was posted in PHP and tagged , . Bookmark the permalink.

4 Responses to List-o-mania

  1. Pingback: Splat the List: A Proposal to support Variadics in PHP’s list language construct | Mark Baker's Blog

  2. Daniel says:

    I definitely learned something new from this. Thanks for the deep dive!

    Like

  3. Pingback: PHP Annotated – June 2022 - makemoneyonlinecom.com

  4. Pingback: PHP Annotated – June 2022 - IMoneyHub.com

Leave a comment