#Article #Типизация #php
#️⃣ Типизация PHP
Наверное буду кэпом, если скажу, что следование строгим типам и избежание неявных преобразований уменьшает магичность кода, что ведёт к стабильности и надёжности.
Поэтому в коде Spiral, Cycle и других наших продуктов мы используем строгую типизацию, где это возможно.
Вроде тема понятная, но... это же PHP. А значит без нюансов не обойтись 💩
⭕️ declare(strict_types=1);
Прям из доки: по умолчанию, PHP будет преобразовывать значения неправильного типа в ожидаемые. ...
Можно включить режим строгой типизации на уровне файла.
В этом режиме, тип значения должен строго соответствовать объявленному, иначе будет выброшено исключение
Единственным исключением из этого правила является передача значения типа
⚠️ На вызовы из внутренних функций, действие
Обратите внимание на предупреждение. Многие core-функции не следуют строгости типов.
Например,
А вот
Рефлексия тоже не следует строгости. Поэтому вместо
Оно, может, чуть медленнее, но зато надёжнее.
Теперь к костылям.
⭕️ Типизация переменных
С помощью несложного текучего костыля можно привязать тип к переменной.
Не делайте так.
⭕️ PHP 8.2
Типы
Зачем это надо? Для вариантности. Например, при расширении метода возвращаемое значение с
Завезли DNF (Disjunctive Normal Form).
Вот такого мутанта теперь можно будет встретить в коде:
ℹ️ Всегда явно указывайте тип nullable параметров (
ℹ️ Тип
ℹ️ Тип
Как же принять callable и записать в свойство? Например так:
Может у вас есть какие-то мысли, нюансы или лайфхаки вокруг типов PHP? Поделитесь в комментариях
#️⃣ Типизация PHP
Наверное буду кэпом, если скажу, что следование строгим типам и избежание неявных преобразований уменьшает магичность кода, что ведёт к стабильности и надёжности.
Поэтому в коде Spiral, Cycle и других наших продуктов мы используем строгую типизацию, где это возможно.
Вроде тема понятная, но... это же PHP. А значит без нюансов не обойтись 💩
⭕️ declare(strict_types=1);
Прям из доки: по умолчанию, PHP будет преобразовывать значения неправильного типа в ожидаемые. ...
Можно включить режим строгой типизации на уровне файла.
В этом режиме, тип значения должен строго соответствовать объявленному, иначе будет выброшено исключение
TypeError
.Единственным исключением из этого правила является передача значения типа
int
туда, где ожидается float
.⚠️ На вызовы из внутренних функций, действие
strict_types
не распространяется.Обратите внимание на предупреждение. Многие core-функции не следуют строгости типов.
Например,
array_map()
и array_filter()
сделают неявное приведение типов.print_r(array_map(fn(int $a, int $b) => $a + $b, [1, '10', 3], [4, 5, '1e2']));
---
Array (
[0] => 5
[1] => 15
[2] => 103
)
А вот
call_user_func()
будет ругаться на несоответствие типов.Рефлексия тоже не следует строгости. Поэтому вместо
newInstanceArgs
/newInstance
в фабрике контейнера у нас $instance = new $class(...$arguments);
.Оно, может, чуть медленнее, но зато надёжнее.
Теперь к костылям.
⭕️ Типизация переменных
С помощью несложного текучего костыля можно привязать тип к переменной.
function makeInt(int &$i): void
{
static $list = [];
$list[] = $obj = new class() {
public int $i;
};
$obj->i = &$i;
}
$int = 1;
makeInt($int);
$int = 42; // 42
$int = 'foo'; // Fatal error: Uncaught TypeError: Cannot assign string...
Не делайте так.
⭕️ PHP 8.2
Типы
null
, false
и true
теперь можно использовать автономно.Зачем это надо? Для вариантности. Например, при расширении метода возвращаемое значение с
bool
можно сузить до true
или false
, а nullable (?Foo
) — до null
. Таких кейсов в библиотеках не мало.Завезли DNF (Disjunctive Normal Form).
Вот такого мутанта теперь можно будет встретить в коде:
(Countable&Traversable)|array
ℹ️
Что интересно, nullable-сахар (?Foo
) был добавлен в PHP 7.1 ещё до Union Types.ℹ️ Всегда явно указывайте тип nullable параметров (
?Foo $foo = null
) а не полагайтесь только на значение null
по умолчанию (Foo $foo = null
).ℹ️ Тип
never
был добавлен в PHP 8.1. Но он вряд ли вам пригодится, если вы гоняете на RoadRunner.ℹ️ Тип
callable
существует только в сигнатурах функций и методов. Его нельзя указать для свойств, а значит и в Promoted properties не засунуть. А всё потому, что в разных контекстах callable
может быть разным.Как же принять callable и записать в свойство? Например так:
// A class declaration
private \Closure $callback;
public function __construct(callable $callback)
{
$this->callback = $callback(...);
}
Может у вас есть какие-то мысли, нюансы или лайфхаки вокруг типов PHP? Поделитесь в комментариях
🔥9
#Article #Типизация #php
#️⃣ Variadic параметр
Вроде тут и сказать нечего. Ну поставил три точки в параметре и всё.
Ан нет. Приколов хватает. Погнали
⭕️ Что известно
Variadic параметр, он же "списки аргументов переменной длины", пользователем с помощью добавления (сюрприз!) многоточия. Примерно столько вы можете прочитать про variadic в документации. А ещё про то, что в него можно распаковывать итерируемые значения другим многоточием.
Variadic является опциональным параметром. Всегда. Т.е. вы можете ничего в него не передавать и ему с того норм.
> Кстати, многие путают понятия "аргумент" и "параметр". Параметр - то, что в определении функции, аргумент - значение, которое передаётся в функцию.
Зачем он нужен, если и так можно передать сколько угодно аргументов, а потом вынуть их с помощью
А затем, что в variadic умеет в типизацию! (про типы см. предыдущий пост).
Теперь только объекты
⭕️ Приколы с распаковкой
Распаковывая iterable в variadic, ключи могут быть разные. Если вы распаковываете список (ключи типа int), то их порядок будет сброшен. Т.е. распаковка массива
Ассоциативный массив останется ассоциативным массивом с теми же ключами.
Но не всегда можно смешивать целочисленные ключи со строковыми. При распаковке аргументов из массива, как и при передаче именованных+позиционных аргументов, позиционные аргументы должны идти перед именованными. Причём сортируется это по параметрам 💥
⭕️ Именованные аргументы (PHP 8.0)
Поиграем в игру. Есть сигнатура
(если нажать на один скрытый элемент — откроются все)
`["c" => 3]`
`Fatal Error`
`["foo" => 3]`
`Fatal Error`
`["c" => 3]`
`Syntax Error`
`[3, 4, "foo" => 5]`
`Fatal Error`
Сколько промахов?
Как итог: распаковка аргументов из массива даёт возможность передать любой сроковой ключ, именованные аргументы — нет.
⭕️ Хрень какая-то. Где оно надо то вообще?
- Когда нужна типизация на списке аргументов, при этом не нужно сохранять ключи. При этом сигнатура точно не будет дополняться. Например, в
- В атрибутах, которые конфижат непонятно что. В атрибуте
⭕️ Аннотации
Артефакты прошлого ещё долго будут встречаться в коде даже современном. Да, это про аннотации. Там variadic'и не поддерживаются. До недавнего времени они вообще фейлили парсер
⭕️ Поддержка IDE
Её нет. Пока нет известного мне способа рассказать IDE или Psalm'у, какие ключи можно передать в variadic. Можно описать тип только одного (каждого) передаваемого элемента. Если вам известно об этом больше - го в комменты.
#️⃣ 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)
- bar(c: [1, 2, 3])
- bar(foo: 3)
- bar(1, 2, a: 3)
- bar(c: 3, a: 1, b: 2)
- bar(foo-bar: 3)
- bar(1, 2, 3, 4, foo: 5)
- bar(b: 2, 3, 4, foo: 5)
- Как итог: распаковка аргументов из массива даёт возможность передать любой сроковой ключ, именованные аргументы — нет.
⭕️ Хрень какая-то. Где оно надо то вообще?
- Когда нужна типизация на списке аргументов, при этом не нужно сохранять ключи. При этом сигнатура точно не будет дополняться. Например, в
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. Можно описать тип только одного (каждого) передаваемого элемента. Если вам известно об этом больше - го в комменты.
🔥5🤯2