Default the List: A Proposal to support default values in PHP’s list language construct

I wrote recently about some changes that I’d like to propose for list() in core PHP for version 8.3: namely that it should work with iterables as well with arrays, and that it should allow a variadic “argument” that would assign all entries that were part of the original array being listed, but that weren’t explicitly assigned in the list.

Those weren’t the only changes that I would like to see implemented for list().

If we used list() to extract entries that don’t exist in the array:


$data = [1 => 'A', 2 => 'B'];

[1 => $firstValue, 2 => $secondValue, 3 => $thirdValue] = $data;

var_dump($firstValue, $secondValue, $thirdValue);

then this will trigger a Notice in PHP 7, promoted to a Warning in PHP 8, and assign a null value to that variable. It becomes even more problematic if we’re assigning those extracted elements to class properties that aren’t nullable:


class Foo {
    private int $firstValue;
    private int $secondValue;
    private int $thirdValue;

    public function __construct(array $data = []) {
        [$this->firstValue, $this->secondValue, $this->thirdValue] = $data;
    }
}

$data = [1, 2];

$foo = new Foo($data);

We still get the Warning (or Notice), but this will also now result in a Fatal Uncaught TypeError because we’re trying to assign that null to the $thirdValue property, which doesn’t allow nulls; making it a less than ideal situation.

So what can we do to prevent this?

Our first option is to use intermediate variables, that can then be validated to ensure that none of them contain a null:


public function __construct(array $data = []) {
    [$firstValue, $secondValue, $thirdValue] = $data;

    $this->firstValue = $firstValue ?? 0;
    $this->secondValue = $secondValue ?? 0;
    $this->thirdValue $thirdValue ?? 0;
}

Not an ideal solution, because it increases the footprint of our code, adding to its complexity, and eliminates many of the benefits of using list() in the first place; and we would still get the Warning (or Notice) appearing in our logs.

Another option is to use a variadic when we instantiate the object (or pass the array to a method), but with the individual values identified in the constructor (or the method definition), and with defaults provided:


class Foo {
    public function __construct(
        private int $firstValue = 0,
        private int $secondValue = 0,
        private int $thirdValue = 0
    ) {
        var_dump($this->firstValue, $this->secondValue, $this->thirdValue);
    }
}

$data = [1, 2];

$foo = new Foo(...$data);

So we can generally pass an array to a method using a variadic, and let the function signature handle setting any default values for us if entries don’t exist in the array. But this will still have problems: although we can specify associative keys by matching the argument names in our method definition, we will still be unable to extract values from enumerated arrays with gaps in the index, so it isn’t a perfect alternative to list().

This approach needn’t be limited to the object constructor, we can also use it with an arrow function as I described in my previous article:


class Foo {
    private int $firstValue;
    private int $secondValue;
    private int $thirdValue;

    public function __construct(array $data = []) {
        $splitData = fn($firstValue = 0, $secondValue = 0, $thirdValue = 0) => [$firstValue, $secondValue, $thirdValue];

        [$this->firstValue, $this->secondValue, $this->thirdValue] = $splitData(...$data);
    }
}

$data = [1, 2];

$foo = new Foo($data);

But the limitations are the same. Methods can only work with the order of arguments for enumerated arrays, or named arguments that match the associative keys in our array; so it will create issues if we have any name/key mismatches, or if an enumerated array isn’t in key order, or if there are gaps in the enumeration.


So wouldn’t it be useful if we could set default values for list() as we do for a method signature? So if an element we wanted to list didn’t exist in the array, we could specify the value to assign for that property.


class Foo {
    private int $firstValue;
    private int $secondValue;
    private int $thirdValue;

    public function __construct(array $data = []) {
        [$this->firstValue, $this->secondValue, $this->thirdValue = 0] = $data;
    }
}

$data = [1, 2];

$foo = new Foo($data);

If no default value was specified, and the element didn’t exist in the $data array, then it would still assign null and issue a Warning; but if we’ve specified the default to be a value (like the 0 value that I’ve specified here), and the array element doesn’t exist, then it would assign that default value to $this->thirdValue, with no Warning, and with no subsequent Fatal Uncaught TypeError; and this would also allow us to set a default value of null that wouldn’t issue a Notice or Warning.


As with my previous proposals for changes to list(), it’s too late to put forward any new proposals for PHP 8.2; but once 8.2 has reached a GA (General Availability) release, then I shall be submitting an RFC (Requests for Comment) to internals for this proposed change to be targeted for the PHP 8.3 release.

This entry was posted in PHP and tagged , , , . Bookmark the permalink.

3 Responses to Default the List: A Proposal to support default values in PHP’s list language construct

  1. Pingback: List-o-mania | Mark Baker's Blog

  2. Pingback: Type the List: A Proposal to support type-casting in PHP’s list language construct | Mark Baker's Blog

  3. Pingback: Splat the List: A Proposal to support Variadics in PHP’s list language construct | Mark Baker's Blog

Leave a comment