Php null safe operator

PHP 8: Оператор безопасного null — nullsafe

PHP 8: Оператор безопасного null - 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, тогда правая часть будет просто отброшена. Это означает, что даже динамические вызовы или вызовы с ошибками позже в процессе не происходят.

Читайте также:  Как поменять версию php xampp

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

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

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

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

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

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

Конечно, у оператора 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 немного сбивает с толку, но к этому легко привыкнуть.

Источник

PHP RFC: Nullsafe operator

This RFC proposes the new nullsafe operator ?-> with full short circuiting.

Proposal

It is fairly common to only want to call a method or fetch a property on the result of an expression if it is not null .

Currently in PHP, checking for null leads to deeper nesting and repetition:

$country = null; if ($session !== null) { $user = $session->user; if ($user !== null) { $address = $user->getAddress(); if ($address !== null) { $country = $address->country; } } } // do something with $country

With the nullsafe operator ?-> this code could instead be written as:

$country = $session?->user?->getAddress()?->country; // do something with $country

When the left hand side of the operator evaluates to null the execution of the entire chain will stop and evalute to null . When it is not null it will behave exactly like the normal -> operator.

Читайте также:  Чем exception отличается от error java

Short circuiting

Introduction

Short circuiting refers to skipping the evaluation of an expression based on some given condition. Two common examples are the operators && and || . There are three ways the nullsafe operator ?-> could implement short circuiting. We’ll look at the same code snippet for every option.

1. Short circuiting for neither method arguments nor chained method calls

This complete lack of short circuiting is currently only found in Hack. Both the function bar() and the method baz() are called. baz() will cause a “Call to a member function on null” error. Evaluating method arguments makes it the most surprising of the three options. This was the primary criticism of the last RFC.

2. Short circuiting for method arguments but not chained method calls

This is what would normally be considered lack of short circuiting. The function bar() is not called, the method baz() is. baz() will cause a “Call to a member function on null” error.

3. Short circuiting for both method arguments and chained method calls

We’ll refer to this as full short circuiting. Neither the function bar() nor the method baz() are called. There will be no “Call to a member function on null” error.

Proposal

This RFC proposes full short circuiting. When the evaluation of one element in the chain fails the execution of the entire chain is aborted and the entire chain evaluates to null . The following elements are considered part of the chain.

Источник

PHP 8.0: Null-safe operator

Null-safe operator is a new syntax in PHP 8.0, that provides optional chaining feature to PHP.

The null-safe operator allows reading the value of property and method return value chaining, where the null-safe operator short-circuits the retrieval if the value is null , without causing any errors.

The syntax is similar to the property/method access operator ( -> ), and following the nullable type pattern, the null-safe operator is ?-> .

Null safe operator silently returns null if the expression to the left side evaluates to null .

class Customer < public function getAddress(): ?Address <>> class Address < public function getCountry(): string <>> $country = $customer->getAddress()->getCountry();

In the snippet above, the Customer::getAddress() method return value is nullable; It can return a null value, or an object of Address class.

The $customer->getAddress()->getCountry() chain is not «null safe», because the return value of getAddress can be null , and PHP throws an error when it tries to call getCountry() method:

Fatal error: Uncaught Error: Call to a member function getCountry() on null in . 

To safely access the address, it was necessary to check the null return values before further accessing the return value.

$address = $customer->getAddress(); $country = $address ? $address->getCountry() : null;
$address = $customer->getAddress(); if ($address) < $country = $address->getCountry(); > else

The null-safe operator solves this by short-circuiting the property/method access, and returning null immediately if the left side of the operator is null , without executing the rest of the expression.

- $address = $customer->getAddress(); - $country = $address ? $address->getCountry() : null; + $country = $customer->getAddress()?->getCountry(); 

The ?-> null-safe operator can help reduce excessive isset() and ternary checks.

Null-safe operator was added to PHP 8.0 between alpha 3 release and first beta release, right around the time PHP 8.0 reached its feature-freeze.

Read-Only

The null-safe operator is read-only. It cannot write/assign values from it.

class Customer < private ?Address $address; public function getAddress(): ?Address < return $this->address; > > class Address < public string $country; >$customer->getAddress()?->country = 'NL';

This snippet attempts to write to the $country property of the Address object. This is not allowed with the null-safe operator.

Fatal error: Can't use nullsafe operator in write context in . on line . 

Chaining

The null-safe operator can be chained, and the expression as a whole will be short-circuited from the first null-safe operator that encounters null.

$customer->getAddress()?->getCoordinates()->getLongitude()->format();

This chain of calls are null-safe. PHP will stop and immediately return null if any of the null-safe return operators evaluate to null .

Null-safe operator must be present at every step the chain can be short-circuited. Using the ?-> operator will not make the whole chain null-safe.

Evaluated from left to right

The null-safe operator is evaluated from left to right. Most importantly, it does not prioritize other function calls or other access patterns such as array-access.

 > class Address < public function setCountry(string $country_code): void <>> $customer->getAddress()?->setCountry(GeoIP::getCountry());

If the Customer::getAddress method returns a valid Address object, the GeoIP::getCountry() will be executed, and passed to the Address::setCountry() method call. Because the null-safe operators are evaluated from left to right, the GetIP::getCountry() method will never be called if the Customer::getAddress() method returns null .

Читайте также:  Выполнение php при нажатии кнопки html

Note that using braces will make the expression inside the braces execute altogether, as opposed to the left-to-write pattern.

$customer->getAddress()?->setCountry((GeoIP::getAddress()?->getCountry()))

If Customer::getAddress() returns a value other than null , the (GeoIP::getAddress()?->getCountry()) chain will be executed, and the return value will be passed to the Address::setCountry() call.

No References

The null-safe operator cannot be used with references.

Fatal error: Cannot take reference of a nullsafe chain in . on line . 

Backwards Compatibility Impact

Null-safe operator is a new syntax, and cannot be back-ported to older PHP versions.

Running code that uses null-safe operator will raise a parse error:

Parse error: syntax error, unexpected '->' (T_OBJECT_OPERATOR) in . on line . 

© 2018-2023 PHP.Watch, with ❤ from Ayesh • About PHP.Watch

Источник

Оцените статью