The list function & practical uses of array destructuring in PHP

2017-05-15 #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!