Sebastian De Deyne
Designer & developer at Spatie

The list function & practical uses of array destructuring in PHP

PHP 7.1 introduced a new syntax for the list() function. I’ve never really seen too much list() calls in the wild, but it enables you to write some pretty neat stuff.

This post is a primer of list() and it’s PHP 7.1 short notation, and an overview of some use cases I’ve been applying them to.

list() 101

The list() construct (it’s actually not a function but a language construct like array()) has been around since PHP 4. list() allows you to pull variables from an array based on their index. Let’s start with a quick primer!

Here’s what a basic list() call looks like:

<?php

list($a, $b, $c) = ['foo', 'bar', 'baz'];

echo $a; // "foo"
echo $b; // "bar"
echo $c; // "baz"

You can give list() less arguments than the array’s size if you don’t care about the rest of the items.

<?php

list($a, $b) = ['foo', 'bar', 'baz'];

echo $a; // "foo"
echo $b; // "bar"

If you try to pull out an index that isn’t set, an undefined offset error is thrown.

<?php

list($a, $b, $c) = ['foo', 'bar'];

echo $a; // "foo"
echo $b; // "bar"
echo $c; // Undefined offset: 1

To skip an intermediate value, you can provide an empty argument.

<?php

list($a, , $b) = ['foo', 'bar', 'baz'];

echo $a; // "foo"
echo $b; // "baz"

As of PHP 7.1, you can specify the key of the item you want to unpack from an associative array. (RFC here)

<?php

$person = [
    'name' => 'Sebastian',
    'job' => 'Developer',
];

list('job' => $job) = $person;

echo $job; // "Developer"

Fun fact: since list() is an assignment operator, you could also assign things in arrays. Not saying this is particularly useful, but it’s possible!

<?php

$people = [
    ['name' => 'Freek', 'role' => 'Developer'],
    ['name' => 'Sebastian', 'role' => 'Developer'],
    ['name' => 'Willem', 'role' => 'Designer'],
];

$names = [];

foreach ($people as $person) {
    list('name' => $names[]) = $person;
}

var_dump($names); // ["Freek", "Sebastian", "Willem"];

The list() operator is pretty cool, but has one caveat: it’s ugly. PHP 7.1 fixes this with some syntactic sugar: plain old vanilla square brackets. These two statements do the same:

<?php

// Oldschool
list($a, $b, $c) = ['foo', 'bar', 'baz'];

// PHP 7.1
[$a, $b, $c] = ['foo', 'bar', 'baz'];

While it’s not as powerful as JavaScript’s array destructuring, it’s still a cool tool to have baked in the language.

Now that we know how array destructuring works, let’s move on to some practical examples.

Exhibit A: Some Classic Unpacking Examples

In this exhibit, we’ll glide over some with some real world situations.

You’ve exploded a string and want to immediately assing the result as two seperate variables.

<?php

[$user, $repository] = explode('/', 'spatie/laravel-medialibrary', 2);

You’ve created a few objects at once, and want them all as their own variables.

<?php

[$userA, $userB, $userC] = factory(User::class, 3)->create();

You want to swap two variables.

<?php

$a = 'hello';
$b = 'world';

[$a, $b] = [$b, $a];

echo $a; // "world"
echo $b; // "hello"

Voila, nothing too fancy here, let’s move over to some more specific use cases, borrowed from other languages.

Exhibit B: Tuples

The most concise definition of a tuple is a “data structure consisting of multiple parts”. Tuples are pretty much arrays with a fixed size and a predefined structure, for example a tuple of a status code and a message, a tuple of a longitude and a latitude, etc.

Tuples are useful in PHP when a key-value pair just doesn’t cut it (maybe we need duplicate keys, or would want more that one value) and when we don’t want to bother creating a value object.

Let’s start with an array of some fruits and vegetables. Every piece of produce has an id, a name and a type.

<?php

$produce = [
    [1, 'apple', 'fruit'],
    [2, 'banana', 'fruit'],
    [3, 'carrot', 'vegetable'],
];

Using the index to access the values requires us to reason about the array contents on every statement. Though process: “This is $item[0], which means is the first item in the array. Next is $item[1], which means it’s the second item, etc."

<?php

$mappedProduce = [];

foreach ($produce as $produce) {
    $mappedProduce[] = [
        'id' => $produce[0],
        'name' => $produce[1],
        'type' => $produce[2],
    ];
}

By destructuring, we can immediately assign the values to a variable, making the contents of the loop clearer. Thought process: “I have an array containing entries that have an id, a name and a type."

<?php

$mappedProduce = [];

foreach ($produce as [$id, $name, $type]) {
    $mappedProduce[] = [
        'id' => $id,
        'name' => $name,
        'type' => $type,
    ];
}

Bonus snippet: we could use compact to create the array with a single statement (although I don’t like compact because it’s just as ugly as list()).

<?php

$mappedProduce = [];

foreach ($produce as [$id, $name, $type]) {
    $mappedProduce[] = compact('id', 'name', 'type');
}

A more real world example: I recently had to make a pretty large form that only contained simple text inputs. I defined every input as a [$name, $value, $required] tuple and looped over all of them.

@foreach([ ['name', $user->name, true], ['telephone', $user->telephone, true],
['fax', $user->fax, false], // ... ] as [$name, $value, $required])
<div>
  <label for="{{ $name }}">{{ __($name) }}</label>
  <input
    type="{{ $text }}"
    name="{{ $name }}"
    id="{{ $name }}"
    value="{{ $value }}"
    @if($required)
    required
    @endif
  />
  <div>@endforeach</div>
</div>

Further reading: Tuples in Python, Tuples in Elixir

Exhibit C: Multiple Returns

Some languages allow multiple returns. Consider a function that expects an integer, and returns two integers, one of them being $input + 5, the other $input - 5. Here’s what that would look like in Go:

func addAndRemoveFive(i int) (int, int) {
    return i + 5, i - 5
}

func main() {
    x, y := addAndRemoveFive(10)
}

We could achieve something similar by returning an array in PHP and immediately destructuring:

<?php

function addAndRemoveFive(int $i): array {
    return [$i + 5, $i - 5];
}

[$x, $y] = addAndRemoveFive(10);

echo $x; // 15
echo $y; // 5

One large downside here: there’s currently no way to document this, neither with PHP 7’s return types nor with PHPDoc (unless both returns have the same type, then you could hint with @return int[]).

A more real world situation where multiple returns can be useful is cases where you’re expecting a status and a message that you may or may not care about, like validation.

<?php

[$valid, $reason] = $validator->validate($data);

if (! $valid) {
    return new JsonResponse(422, ['reason' => $reason]);
}

return new JsonResponse(200);

The previous example could also be interpreted as a tuple containing a status and a reason.

Further reading: Multiple returns in Go

That’s It!

That concludes today’s presentation. I’ll update the post in the future when I come accross other nifty use cases. Meanwhile, feel free to hit me up on Twitter if you’re using list() in other cool ways!

If you enjoyed this post, you might be interested in my newsletter. I occasionally send a dispatch with personal stories, things I’ve been working on in the past month, and other interesting tidbits I come across online.