ПХП и строгая типизация
В ПХП много странностей, ещё одна дала о себе знать в неожиданном месте. Сначала немного теории.
Во многих языках дозволяется определять функции, методы или их аналоги с необязательными параметрами, их при вызове можно не указывать способом, предусмотренным языком. По моим ощущениям наиболее распространён способ с присвоением таким параметрам значений по-умолчанию, которые они получают, если другие значения не были указаны при вызове.
ПХП использует эту схему везде, кроме расширений и встроенных функций. Понятие «необязательный параметр» там есть, но обрабатывается иначе — у параметра указывается тип (например «строка»), необязательность и «нулабельность» (можно ли в этом параметре принимать 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);
}
Странно то, что у необязательного параметра нет никакого значения по-умолчанию, которое можно было бы указать. В принципе, даже если бы оно было, это тоже не очень удобно.
Не смог найти, но я помню, что было чьё-то предложение расширить синтаксис ПХП — разрешить при вызове функции или метода использовать ключевое слово «default» для указания, что в данном месте нужно использовать значение по-умолчанию. Мне кажется тут бы оно пригодилось.
Определённо пригодилось бы. Тоже наткнулся на это...
С php 7.1 в тайпхинтинге можно указывать nullable объекты.
public function put(Serialized $object, string $eventName, $extraEventData, ?string $uniqueId = «defaultValue»)
$uniqueId может быть null, если передать его явно и содержит значение по умолчанию.
Комментарий для Alexey:
Тут речь не о методе put, а о том, что происходит дальше — вызове doBackground, где $uniqueId = null. Проблемы начинаются там.
Комментарий для Alexey:
Нотация «type $var = null» так же указывает, что null тут можно использовать, в этом месте проблемы нет.
$uniqueId !== null && array_push($args, $uniqueId);
Комментарий для hshhhhh.name:
В чём преимущество «array_push» перед $args[]? Зачем использовать сторонний эффект && (частичное вычисление), когда можно использовать обычный «if»?
О, еще один Alexey появился >_>