Копаясь в статанализе в рамках проекта Typhoon, мы про каждую мелочь стараемся думать во всей полноте, чтобы не упустить даже очень редко используемые возможности PHP. Так мы периодически узнаём что-то новое.
Например, есть тип
callable(<params>): <return type>. У параметров есть следующие модификаторы: = (со значением по умолчанию), ... (вариадический), & (передаётся по ссылке). Вопрос: как их можно сочетать?1. Параметры, передаваемые по ссылке, очевидно, могут иметь значение по умолчанию.
2. Вариадические, очевидно, не могут иметь значения по умолчанию.
3. Могут ли вариадики передаваться по ссылке? Вот тут лично для меня уже неочевидно, потому что я вообще редко что-то передаю по ссылке.
Разбираемся. Вариадические функции добавил Никита Попов в далёком 2013 году (до этого все использовали
func_get_args()), и вот что написано в RFC:By-reference capture
The new syntax additionally adds support for capturing variadic arguments by-reference, something that was previously not possible in userland code. Only internal functions could make use of this viaZEND_ACC_PASS_REST_BY_REF.
This would allow implementing functions likesscanf()ormysqli_stmt::bind_param()in userland.
A by-reference capture of variadic arguments is indicated by a&before the ellipsis....
Пробуем:
function increment(int &...$ints): void
{
foreach ($ints as &$int) {
++$int;
}
}
$a = 0;
$b = 100;
increment($a, $b);
var_dump($a, $b); // 1, 101
Получается, в параметрах типа
callable возможны только два сочетания: &... и &=.Кстати, обратите внимание: слово "опциональный" не является точным определением для
= параметра. Опциональные параметры — это те, которые можно не передавать, к ним относятся не только параметры со значением по умолчанию, но и вариадические. Поэтому = однозначно определяется именно как параметр со значением по умолчанию (у нас в коде bool $hasDefault, а не $isOptional). Этот нюанс хорошо прослеживается в нативной рефлексии:
$reflection = new ReflectionParameter('increment', 'ints');
var_dump(
$reflection->isOptional(), // true
$reflection->isDefaultValueAvailable(), // false
);