ПХП и строгая типизация
В ПХП много странностей, ещё одна дала о себе знать в неожиданном месте. Сначала немного теории.
Во многих языках дозволяется определять функции, методы или их аналоги с необязательными параметрами, их при вызове можно не указывать способом, предусмотренным языком. По моим ощущениям наиболее распространён способ с присвоением таким параметрам значений
ПХП использует эту схему везде, кроме расширений и встроенных функций. Понятие «необязательный параметр» там есть, но обрабатывается иначе — у параметра указывается тип (например «строка»), необязательность и «нулабельность» (можно ли в этом параметре принимать null в качестве значения).
Последнее очень полезно для числовых и булевых типов — если «нулабельность» не указана, то null будет преобразован по правилам языка в значение указанного типа.
У многих функций ПХП в документации указаны значения, которые будут подставлены, если параметр не указан. В тех случаях, когда такое значение не указано, можно было попытаться подставить null, многие расширения это проглатывают.
Например, у нас преспокойно работал примерно такой код:
public function put(Serialized $object, $eventName, $extraEventData, $uniqueId = null)
{
return DI::gearman_client()->doBackground(
$this->queueName,
igbinary_serialize(
[
'object' => $object,
'event_data' => $extraEventData,
'event_name' => $eventName,
]
),
$uniqueId
);
}Всё работало корректно, пока не пришёл ПХП7 и мы не стали потихоньку переползать на строгую типизацию. Вечером я закоммитил изменения в этом файле, которые позволили включить строгую типизацию, а за завтраком поймал в логах странную ошибку, которая сообщала мне, что в метод doBackground время от времени получает в качестве последнего параметра null, а так нельзя.
Сначала я недоумевал, а потом догадался, что случилось — у doBackground последний, необязательный парамер имеет тип «строка» и он не «нулабельный». То есть в строгой типизации я его должен либо не передавать вовсе, либо передавать туда исключительно строку. А null, который передавался туда до перехода на строгую типизацию более не подходит, ибо он не строка.
Пришлось переписать более уродливо:
public function put(Serialized $object, string $eventName, $extraEventData, string $uniqueId = null)
{
$args = [
$this->queueName,
igbinary_serialize(
[
'object' => $object,
'event_data' => $extraEventData,
'event_name' => $eventName,
]
),
];
if ($uniqueId !== null) {
$args[] = $uniqueId;
}
return DI::gearman_client()->doBackground(...$args);
}Странно то, что у необязательного параметра нет никакого значения
Не смог найти, но я помню, что было
Комментарии 7
public function put(Serialized $object, string $eventName, $extraEventData, ?string $uniqueId = «defaultValue»)
$uniqueId может быть null, если передать его явно и содержит значение по умолчанию.