0

Array destructuring with list() and []

Intermediate5 min read·php-04-006
compare

Concept

Array destructuring is PHP's syntax for extracting multiple values from an array in a single expression. PHP 7.1 extended the original list() construct with the short [] syntax for destructuring, and the two forms are interchangeable. Both compile to the same opcodes. Destructuring is a compile-time optimization — the engine accesses the specified offsets directly without building intermediate arrays.

The list() construct has been in PHP since version 3. It accepts a comma-separated list of variables and maps them to the corresponding positional elements of the right-hand array, left-to-right by index. Holes are supported by leaving positions empty. Prior to PHP 7.1, list() always worked by index — you could not destructure by key. PHP 7.1 added key-based destructuring: ['key' => $var], which extracts the value at 'key' regardless of array position.

A critical behavioral difference: list() and [] in assignment context do NOT modify the source array — they only read it. They trigger PHP's copy-on-write mechanism if the source array is referenced elsewhere, but otherwise the source array remains unchanged. Destructuring also works in foreach loops via foreach ($rows as ['id' => $id, 'name' => $name]), which is the idiomatic way to unpack database rows in PHP 8.4.

Nested destructuring is supported: [[$a, $b], [$c, $d]] = $matrix. Each nesting level maps to the corresponding nesting level of the source array. This is particularly useful for unpacking coordinates, key-value pairs, or tuple-like return values from functions that return multiple pieces of data.

SyntaxPositionalBy keyWorks in foreach
list($a, $b)YesNo (< 7.1)Yes
[$a, $b]YesNoYes
list('k' => $v)N/AYes (7.1+)Yes
['k' => $v]N/AYes (7.1+)Yes

Code Example

php
<?php
declare(strict_types=1);

// ---- Basic positional destructuring ----
[$first, $second, $third] = [10, 20, 30];
echo $first;  // 10
echo $second; // 20

// ---- list() — identical semantics ----
list($x, $y) = [100, 200];

// ---- Skipping elements with holes ----
[, $middle, ] = ['before', 'target', 'after'];
echo $middle; // target

// ---- Key-based destructuring (PHP 7.1+) ----
$user = ['id' => 42, 'name' => 'Alice', 'email' => 'alice@example.com'];
['id' => $id, 'name' => $username] = $user;
echo "$id: $username"; // 42: Alice

// ---- Key-based: order doesn't matter ----
['email' => $email, 'id' => $uid] = $user;
// Works correctly regardless of key order in source array

// ---- In foreach — idiomatic for DB rows ----
$rows = [
    ['id' => 1, 'name' => 'Alice'],
    ['id' => 2, 'name' => 'Bob'],
];
foreach ($rows as ['id' => $id, 'name' => $name]) {
    echo "$id: $name\n";
}

// ---- Nested destructuring ----
$matrix = [[1, 2], [3, 4]];
[[$topLeft, $topRight], [$bottomLeft, $bottomRight]] = $matrix;
echo $bottomRight; // 4

// ---- Swap variables without temp var ----
$a = 'hello';
$b = 'world';
[$a, $b] = [$b, $a];
echo "$a $b"; // world hello

// ---- Function returning multiple values ----
function minMax(array $nums): array
{
    return [min($nums), max($nums)];
}
[$min, $max] = minMax([5, 1, 9, 3]);
echo "$min - $max"; // 1 - 9

// ---- Partial destructuring — unneeded keys are simply ignored ----
$response = ['status' => 200, 'body' => '...', 'headers' => []];
['status' => $status] = $response; // only extract what you need

Interview Q&A

Q: What is the difference between [$a, $b] = $arr and ['x' => $a, 'y' => $b] = $arr, and what happens if a key is missing in the key-based form?

Positional destructuring [$a, $b] = $arr maps $a to $arr[0] and $b to $arr[1] regardless of any string keys in $arr. Key-based destructuring ['x' => $a, 'y' => $b] = $arr extracts $a = $arr['x'] and $b = $arr['y'], regardless of key order. If a key is missing in the key-based form, PHP emits an E_WARNING (undefined array key) and assigns null to the variable. To be safe with untrusted data, validate with array_key_exists() before destructuring, or use the ?? operator on the values after extraction.


Q: How does foreach ($rows as ['id' => $id, 'name' => $name]) differ from foreach ($rows as $row) followed by variable extraction inside the loop body? Is there a performance difference?

The destructuring foreach is compiled to direct HashTable key lookups on each iteration and assigns directly to the named local variables. It is slightly more efficient than $row = ... followed by extract($row) because extract() iterates over all keys of the array and calls symbol-table insertion for each, which is O(k) with higher constant factor. More importantly, extract() has security implications — if $row contains a key like this or _ or a variable name you already use, extract() silently overwrites it. The destructuring form is explicit, safe, and idiomatic in PHP 8.4.


Q: PHP does not have native tuple types. How does the [..] = fn() destructuring pattern simulate tuples, and what are its limitations compared to languages like Python or Go?

A function can return a fixed-length indexed array return [$value, $error], and the caller destructures it [$value, $error] = doSomething(). This simulates tuples syntactically. The limitations versus Python or Go are: (1) PHP has no type enforcement on the returned array's structure — the function could return 3 elements or wrong types without compile-time error; (2) there is no way to name the "tuple fields" without switching to an associative return or a value object; (3) static analysis tools like PHPStan and Psalm can partially understand these patterns via @return array{int, string} shape annotations, but it requires explicit annotation. For robust multi-value returns in PHP 8.4 codebases, a named DTO or readonly class is preferable to array tuples for anything beyond trivial cases.