rfc:list_keys

This is an old revision of the document!


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 constants:

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

However, keys cannot be variables or other expressions:

// Parse error: syntax error, ...
list($foo => $bar) = $array;

The problem here is that there are two different behaviours that you might expect 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 is not allowed in order to prevent misunderstanding. (However, this might change: see Open Issues below.)

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" => ($x1, $y1), "second" => ($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.

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.

Open Issues

Due to the aforementioned potential confusion, arbitrary expressions (including variables) are not accepted as keys, merely constants. However, if we think that the two possibilities are unlikely to be confused, we could allow variable keys. This would be mostly useful for ArrayAccess-implementing objects, which can use objects as keys. Allowing arbitrary expressions here would be trivial to implement.

Unaffected PHP Functionality

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

Future Scope

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 function 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.

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.

Proposed Voting Choices

The vote will be 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.

Patches and Tests

I have written a complete patch with tests: https://github.com/php/php-src/compare/master...TazeTSchnitzel:list_keys

There is not yet a patch for the language specification.

Implementation

After the project is implemented, this section should contain

  1. the version(s) it was merged to
  2. a link to the git commit(s)
  3. a link to the PHP manual entry for the feature

References

Rejected Features

None as yet.

Changelog

  • v1.0 - First announced version
rfc/list_keys.1452998397.txt.gz · Last modified: 2017/09/22 13:28 (external edit)