rfc:list_keys

PHP RFC: Allow specifying keys in list()

Introduction

Unpacking array elements into variables is a common operation in certain types of PHP code. For example, it is common to use an array as an “argument bag” when initialising an object:

class ElePHPant
{
    private $name, $colour, $age, $cuteness;
 
    public function __construct(array $attributes) {
        $this->name = $attributes["name"];
        $this->colour = $attributes["colour"];
        $this->age = $attributes["age"];
        $this->cuteness = $attributes["cuteness"];
    }
 
    // ...
}

However, doing so line-by-line, as in the above example, is somewhat cumbersome, because the name of the array must be repeated each line.

Apparently ideal for this use-case, PHP has a language construct for unpacking multiple array elements into variables, namely list(), which is used like so:

list($first, $second, $third) = $someArray;

And is equivalent to:

$first = $someArray[0];
$second = $someArray[1];
$third = $someArray[2];

Unfortunately for use cases like our first example however, list() has no support for unpacking array elements with specified keys, instead it only works for numbered keys starting from zero.

This RFC proposes to introduce a way to specify keys in the list() construct, such that our first example could be rewritten like so, behaving identically:

class ElePHPant
{
    private $name, $colour, $age, $cuteness;
 
    public function __construct(array $attributes) {
        list(
            "name" => $this->name,
            "colour" => $this->colour,
            "age" => $this->age,
            "cuteness" => $this->cuteness
        ) = $attributes;
    }
 
    // ...
}

Proposal

Instead of having implied numbered keys from zero, list() can also now contain explicit keys, instead.

As well as being strings, these keys can also be numbers:

$powersOfTwo = [1 => 2, 2 => 4, 3 => 8];
list(1 => $oneBit, 2 => $twoBit, 3 => $threeBit) = $powersOfTwo;

Keys can not only be literals like 2 or "foo", but also any expression , including constants or variables (this was not the case in an earlier revision of the RFC, see Resolved issues).

list(
    CURLOPT_GET => $isGet,
    CURLOPT_POST => $isPost,
    CURLOPT_URL => $url
) = $curlOptions;
list($foo => $bar) = $bar;

list() elements with and without keys cannot be mixed, unlike in the array() syntax:

// Parse error: syntax error, ...
list($unkeyed, "key" => $keyed) = $array;

Implementing this would be more complicated, and it also is not very useful behaviour given arrays typically have either explicit (usually string) keys or implicit sequential numbered keys, not both.

Empty elements are not allowed where keys are specified, because there is no need for them:

// Parse error: syntax error, ...
list(,,,, "key" => $keyed) = $array;

However, a trailing comma is permitted in list() with keys, like in array():

list(
    CURLOPT_GET => $isGet,
    CURLOPT_POST => $isPost,
    CURLOPT_URL => $url,
) = $curlOptions;

Nesting of list() is still possible for this syntax, including nesting a list() with keys inside a list() without keys, and vice-versa:

$points = [
    ["x" => 1, "y" => 2],
    ["x" => 2, "y" => 1]
];
 
list(list("x" => $x1, "y" => $y1), list("x" => $x2, "y" => $y2)) = $points;
 
$points = [
    "first" => [1, 2],
    "second" => [2, 1]
];
 
list("first" => list($x1, $y1), "second" => list($x2, $y2)) = $points;

Explicit keys in list() also work in foreach statements, as does every other feature of list():

$points = [
    ["x" => 1, "y" => 2],
    ["x" => 2, "y" => 1]
];
 
foreach ($points as list("x" => $x, "y" => $y)) {
    echo "Point at ($x, $y)", PHP_EOL;
}

As with regular list(), objects implementing ArrayAccess are supported.

Handling of implicit conversions of keys, and of accessing undefined keys, follows the same rules as for regular array indexing.

Further Examples

The use of explicit integer keys can be clearer than regular un-keyed list() in the same situations. Compare these two code snippets performing routing:

$result = $dispatcher->dispatch($httpMethod, $uri);
switch ($result[0]) {
    case \FastRoute\Dispatcher::FOUND:
        list(, $handler, $parts) = $result;
 
        // ...
}
$result = $dispatcher->dispatch($httpMethod, $uri);
switch ($result[0]) {
    case \FastRoute\Dispatcher::FOUND:
        list(1 => $handler, 2 => $parts) = $result;
 
        // ...
}

The comma in the version using implicit keys could be missed when reading the code, and here we are mixing two different kinds of indexing: explicit ($result[0]) and positional (list(, $handler, $parts). In the version using explicit keys, it is harder to miss that the second and third element of the array are being used, and all three keys are expressed in the same, explicit fashion.

Backward Incompatible Changes

None. The existing list() continues to work.

Proposed PHP Version(s)

This is proposed for the next minor or major PHP version, currently PHP 7.1.

RFC Impact

To SAPIs

No specific impact I am aware of.

To Existing Extensions

PHP extensions generally do not directly interact with the language syntax, so impact here is unlikely.

However, the addition of this syntax means that the content of the ZEND_AST_LIST abstract syntax tree node will now be different for arrays with keys, so this will affect extensions that deal with the AST.

To Opcache

I have not yet verified the RFC's compatibility with opcache. The ZEND_FETCH_LIST opcode can now take a string OP2, and OP2 can now be TMPVAR as well as CONST (so that constants resolved at runtime work), which might create problems if Zend Optimizer handles this opcode.

New Constants

No new constants are introduced.

php.ini Defaults

No impact on php.ini.

Resolved Issues

Should arbitrary expressions be allowed as keys?

Given this syntax, it was thought there might be potential confusion as to what $foo does:

list($foo => $bar) = $array;

The problem was that there were two different behaviours that might be expected here:

  1. $foo is set to the key of the first element in $array, and $bar is set to its key $array (i.e. $bar = reset($array); $foo = key($array);)
  2. $bar is set to the value of the element in $array with the key $foo (i.e. $bar = $array[$foo];)

So, this was initially not allowed in order to prevent misunderstanding. However, after discussions and further thought, it seemed as though misinterpreting it as doing the first thing (taking the key of the first element) was unlikely, and so we do not need to restrict arbitrary expressions. It also is more useful to accept them, since this means things like object keys (e.g. with SplObjectStorage) can work.

Open Issues

None.

Unaffected PHP Functionality

This does not impact the array() syntax or the behaviour of array indexing.

Future Scope

The following is merely potential future scope and is not part of the proposal proper, nor being voted on in this RFC.

It would be useful to support list() in function parameter lists, so that an “argument bag” parameter could be immediately destructured:

class ElePHPant
{
    private $name, $colour, $age, $cuteness;
 
    public function __construct(list("name" => $name, "colour" => $colour, "age" => $age, "cuteness" => $cuteness)) {
        $this->name = $name;
        $this->colour = $colour;
        $this->age = $age;
        $this->cuteness = $cuteness;
    }
 
    // ...
}
 
$myElePHPant = new ElePHPant(["name" => "Andrea", "colour" => "fuchsia", "age" => 19, "cuteness" => rand()]);

This would be more practical to implement than the Named Parameters RFC, and could be applied to existing functions which already use the “argument bag” pattern. Destructuring syntax in parameter declarations is a feature in some other programming languages, for example in ECMAScript 6. Destructuring of arrays is also a subset of what is possible with pattern matching, which is a feature of many functional programming languages.

If we implemented this, we might want to extend the syntax to support type declarations:

class ElePHPant
{
    private $name, $colour, $age, $cuteness;
 
    public function __construct(list("name" => string $name, "colour" => \Colour $colour, "age" => int $age, "cuteness" => float $cuteness)) {
        $this->name = $name;
        $this->colour = $colour;
        $this->age = $age;
        $this->cuteness = $cuteness;
    }
 
    // ...
}

It was the future possibility of using list() in function parameter lists that motivated the creation of this RFC. It is not part of this RFC, however, but would be proposed in a subsequent RFC if this one passes.

It has been suggested that at some point list() may be given an alternate syntax of [], just as happened for array(). This would mean that there would be symmetry between the syntax for creating an array ([1, 2, 3]) and destructuring it ([$a, $b, $c]), as there is in some other programming languages. This proposal to allow specifying keys would not present any issues for this alternate syntax that I am aware of.

Vote

The vote is a simple Yes/No vote on whether to accept the RFC and merge the patch into PHP master. As this adds a feature to the language, this RFC requires a 2/3 majority to succeed.

Voting started on 2016-02-05 and ended on 2016-02-14.

Accept the Allow specifying keys in list() RFC for PHP 7.1, and merge the patch into master?
Real name Yes No
ajf (ajf)  
bishop (bishop)  
bwoebi (bwoebi)  
danack (danack)  
davey (davey)  
derick (derick)  
dm (dm)  
dmitry (dmitry)  
fmargaine (fmargaine)  
francois (francois)  
googleguy (googleguy)  
gooh (gooh)  
guilhermeblanco (guilhermeblanco)  
jhdxr (jhdxr)  
kguest (kguest)  
krakjoe (krakjoe)  
laruence (laruence)  
leigh (leigh)  
levim (levim)  
marcio (marcio)  
mariano (mariano)  
mbeccati (mbeccati)  
mike (mike)  
ocramius (ocramius)  
pajoye (pajoye)  
salathe (salathe)  
ssb (ssb)  
stas (stas)  
svpernova09 (svpernova09)  
thijs (thijs)  
trowski (trowski)  
weierophinney (weierophinney)  
yohgaki (yohgaki)  
zimt (zimt)  
Final result: 23 11
This poll has been closed.

Patches and Tests

php-src has a complete pull request including tests: https://github.com/php/php-src/pull/1730

There is a language specification pull request, also with tests: https://github.com/php/php-langspec/pull/152

Implementation

Merged into php-src for PHP 7.1: https://github.com/php/php-src/commit/37c8bb58686b2d86f145ebe4fe39854f5951dcd7

Merged into php-langspec for PHP 7.1: https://github.com/php/php-langspec/commit/0b1a497a6c847f6566f32057f55259f68bb9ce38

After the project is implemented, this section should contain

  1. a link to the PHP manual entry for the feature

References

Rejected Features

None as yet.

Changelog

  • v1.1.1 (2016-02-12) - Added further example
  • v1.1 - Resolved issue of whether to permit arbitrary expressions in keys (yes)
  • v1.0 - First announced version
rfc/list_keys.txt · Last modified: 2017/09/22 13:28 (external edit)