PHP 8: Оператор nullsafe

PHP 8: Оператор nullsafe

Безопасный вызов методов

Давайте будем честными, нулевые значения - отстой. Их называют ошибкой на миллиард долларов, но программистам на большинстве языков все еще приходится с ней сталкиваться

Если вы раньше использовали оператор объединения с нулевым значением (??), вы, вероятно, также заметили его недостатки: объединение с нулевым значением не работает при вызовах методов. Вместо этого вам нужны промежуточные проверки или полагаться на какие-то хелперы, предоставляемых некоторыми фреймворками:


$startDate = $booking->getStartDate();

$dateAsString = $startDate ? $startDate->asDateTimeString() : null;

Оператор nullsafe обеспечивает функциональность, аналогичную объединению null, но также поддерживает и вызовы методов. Возьмем для примера следующий код: 


$country =  null;

if ($session !== null) {
$user = $session->user;

if ($user !== null) {
$address = $user->getAddress();

if ($address !== null) {
$country = $address->country;
}
}
}

Это, конечно некрасиво и к тому же трудно читать, но необходимо, если вы хотите избежать ужасной ошибки Call to a member function on null.

В идеальном мире мы могли бы структурировать наш код так, чтобы null был невозможен. К сожалению, мы живем не в идеальном мире, особенно когда нам приходится иметь дело с чужим кодом. По этой причине в ряде языков есть так называемый «оператор nullsafe». И, начиная с версии 8.0, PHP является одним из них. И PHP 8 позволит написать:


$country = $session?->user?->getAddress()?->country;

В этом примере значение $session может быть как объектом класса Session так и null, а $user является либо объектом User, либо null. Возвращаемое значение - User::getAddress() это либо объект Address, либо null. Если $session?->user возвращает значение null, то  игнорируется вся остальная часть строки. То же самое и с getAddress(). В конце концов, $country это либо допустимое значение страны, полученное из свойства country адреса, либо оно равно нулю.

Давайте посмотрим, что может и что не может этот новый оператор!  

Начнем с ответа на самый важный вопрос: в чем именно разница между оператором объединения null и оператором nullsafe? Давайте посмотрим на этот пример:


class Order
{
public ?Invoice $invoice = null;
}

$order = new Order();

Здесь у нас есть объект Order, который имеет необязательное отношение к объекту Invoice. Теперь представьте, что мы хотели бы получить номер счета (если счет не нулевой). Вы можете сделать это как с оператором объединения null, так и с оператором nullsafe:


var_dump($order->invoice?->number);
var_dump($order->invoice->number ?? null);

Так в чем тогда разница? Хотя в этом примере вы можете использовать оба оператора для достижения одного и того же результата, у них также есть определенные граничные случаи, с которыми может справиться только один из них. Например, вы можете использовать оператор объединения null в сочетании с ключами массива, в то время как оператор nullsafe не может их обрабатывать:


$array = [];

var_dump($array['key']->foo ?? null);

 

var_dump($array['key']?->foo);

Warning: Undefined array key "key"

С другой стороны, оператор nullsafe может работать с вызовами методов, а оператор объединения null - нет. Представьте себе такой Invoice объект:


class Invoice
{
public function getDate(): ?DateTime { /* … */ }

// …
}

$invoice = new Invoice();

Вы можете использовать оператор nullsafe для вызова format() даты счета-фактуры, даже если она null:


var_dump($invoice->getDate()?->format('Y-m-d'));

// null

В то время как оператор объединения с нулевым значением выйдет из строя:


var_dump($invoice->getDate()->format('Y-m-d') ?? null);

Fatal error: Uncaught Error: Call to a member function format() on null

Короткое замыкание и isset

Иногда вы можете использовать либо оператор объединения с нулевым значением, либо оператор nullsafe, в других случаях вам нужно будет сделать конкретный выбор. Разница в том, что оператор nullsafe, по сути, использует форму «короткого замыкания»: запись ?-> заставит PHP смотреть на то, что находится в левой части этого оператора, и если она равна null, тогда правая часть будет просто отброшена. Это означает, что даже динамические вызовы или вызовы с ошибками позже в процессе не происходят.

А вот оператор объединения null (??) на самом деле является замаскированным isset вызовом своего левого операнда, который не поддерживает короткое замыкание.

Короткое замыкание также означает, что при написании чего-то вроде этого:


$foo?->bar(expensive_function());

expensive_function будет выполнена, только если $foo не будет равен null.

Вложенные операторы nullsafe

Можно вложить несколько вызовов операторов nullsafe следующим образом:


$foo?->bar?->baz()?->boo?->baa();

Только для чтения данных 

Конечно, у оператора nullsafe есть несколько ограничений. Он работает только для чтения свойств или методов, но не для записи значений. Он также не работает с ссылками, потому что ссылки требуют реальных значений. (Это согласуется с другими языками, в которых есть нулевой безопасный оператор.)

Итак, запомните, вы не можете использовать оператор nullsafe для записи данных в объекты, т.е. такая запись не пройдет:


$offer?->invoice?->date = new DateTime();

Он также не указывает на то, какой шаг в цепочке вернул null. Возьмем тот же пример с country:


$country = $session?->user?->getAddress()?->country;

 Если $country будет равен null, это может быть потому что $session был null, или $user был null, или getAddress() возвратил null. Короче, невозможно сказать. Иногда это нормально. Однако, если вам нужно точно это знать, nullsafe вам не поможет. Для этого вам нужна Either монада, но это тема совсем другого времени...

Тем не менее, nullsafe может помочь уменьшить количество шаблонного кода, плавающего вокруг цепочек объектно-ориентированных методов. Мы должны поблагодарить Илью Товило за nullsafe RFC. Оператор nullsafe определенно является нужной, и недостающей частью, наконец добавленной в PHP. Учитывая его динамичный характер, приятно иметь чистый, красивый способ справиться с null.

Поначалу разница и совпадение между оператором nullsafe и оператором объединения null немного сбивает с толку, но к этому легко привыкнуть.

Сергей Мухин

Веб-разработчик со стажем программирования более 9 лет, всегда в процессе учебы и созидания.

Есть вопросы?

Я почти всегда в режиме онлайн

Связаться со мной