#️⃣ Variadic параметр
Вроде тут и сказать нечего. Ну поставил три точки в параметре и всё.
Ан нет. Приколов хватает. Погнали
⭕️ Что известно
Variadic параметр, он же "списки аргументов переменной длины", пользователем с помощью добавления (сюрприз!) многоточия. Примерно столько вы можете прочитать про variadic в документации. А ещё про то, что в него можно распаковывать итерируемые значения другим многоточием.
function toArray(...$items): array { return $items; }
$a = ['foo', 'bar'];
toArray(...$a);Variadic является опциональным параметром. Всегда. Т.е. вы можете ничего в него не передавать и ему с того норм.
> Кстати, многие путают понятия "аргумент" и "параметр". Параметр - то, что в определении функции, аргумент - значение, которое передаётся в функцию.
Зачем он нужен, если и так можно передать сколько угодно аргументов, а потом вынуть их с помощью
func_get_arg(), как делали ещё наши предки? Кроме того, мы можем в конце списка параметров сунуть массив и даже распаковка не понадобится при вызове функции. function foo(Foo $a, array $myVariadic = []) {}.А затем, что в variadic умеет в типизацию! (про типы см. предыдущий пост).
function foo(Foo $a, Bar ...$variadic) {}Теперь только объекты
Bar попадут в массив $variadic. Ну это и так все знали.⭕️ Приколы с распаковкой
Распаковывая iterable в variadic, ключи могут быть разные. Если вы распаковываете список (ключи типа int), то их порядок будет сброшен. Т.е. распаковка массива
[3 => 'a', 2 => 'b', 1 => 'c'] приведёт к ['a', 'b', 'c'] в вариадике.Ассоциативный массив останется ассоциативным массивом с теми же ключами.
Но не всегда можно смешивать целочисленные ключи со строковыми. При распаковке аргументов из массива, как и при передаче именованных+позиционных аргументов, позиционные аргументы должны идти перед именованными. Причём сортируется это по параметрам 💥
⭕️ Именованные аргументы (PHP 8.0)
Поиграем в игру. Есть сигнатура
bar(int $a = 1, int $b = 2, int ...$c). Что попадёт в $c?(если нажать на один скрытый элемент — откроются все)
bar(a: 1, c: 3) - `["c" => 3]`bar(c: [1, 2, 3]) - `Fatal Error`bar(foo: 3) - `["foo" => 3]`bar(1, 2, a: 3) - `Fatal Error`bar(c: 3, a: 1, b: 2) - `["c" => 3]`bar(foo-bar: 3) - `Syntax Error`bar(1, 2, 3, 4, foo: 5) - `[3, 4, "foo" => 5]`bar(b: 2, 3, 4, foo: 5) - `Fatal Error`Сколько промахов?
Как итог: распаковка аргументов из массива даёт возможность передать любой сроковой ключ, именованные аргументы — нет.
⭕️ Хрень какая-то. Где оно надо то вообще?
- Когда нужна типизация на списке аргументов, при этом не нужно сохранять ключи. При этом сигнатура точно не будет дополняться. Например, в
yiisoft/injector в конструктор можно было бы передавать переменное число ContainerInterface, а не один и только один (@samdark, я смотрю на тебя). Или вот пример, как заставить передать минимум один аргумент нужного типа: function setEncoders(Encoder $encoder, Encoder ...$encoders) {}.- В атрибутах, которые конфижат непонятно что. В атрибуте
Column компонента cycle/orm можно передать любые именованные аргументы сверх тех, что предопределены заранее (спасибо, variadic). Например, определяем #[Column(type: 'bigint', unsigned: true). Здесь unsigned попадёт в variadic и, если у вас MySQL, столбец будет unsigned.⭕️ Аннотации
Артефакты прошлого ещё долго будут встречаться в коде даже современном. Да, это про аннотации. Там variadic'и не поддерживаются. До недавнего времени они вообще фейлили парсер
doctrine/annotations, однако там решили ограничиться просто фиксом "чтоб не падало". Поэтому (и не только) не пользуйтесь аннотациями, если есть возможность пользоваться атрибутами.⭕️ Поддержка IDE
Её нет. Пока нет известного мне способа рассказать IDE или Psalm'у, какие ключи можно передать в variadic. Можно описать тип только одного (каждого) передаваемого элемента. Если вам известно об этом больше - го в комменты.