Шпаргалка

Я люблю краткие записи и часто пользуюсь тернарным оператором, особенно в PHP. Добавление новых 2х вариаций тернарного оператора, таких как:

  • ?: — сокращенная запись тернарного оператора
  • ?? — null coalescing оператор

добавили приятного удобства, с одной стороны. С другой — я сам лично периодически путаюсь в этих операторах и забываю какой как должен себя повести. Но так же я видел что некоторые отъявленные php-гуру задают вопросы на по этим операторам. Решил немного привести знания в порядок и структурировать их.

Тернарный оператор

И так, тернарный оператор в классическом виде это не что иное как:

<?= true ? 'true' : false ? 'yes' : 'no' ?>

Выглядит вроде бы просто и понятно (или нет?). И такое можно встретить не только на собеседовании, но и в работе. Вопрос — а каков результат?

Если вычислять, то вроде бы по логике должно вывести слово true . Но это не так. Будет выведено yes . Как так?

Все дело в том, что тернарные выражения вычисляются слева направо. Поэтому эту запись более очевидно можно описать так:

<?= (true ? 'true':false) ? 'yes':'no' ?>

Вы можете видеть, что первое выражение вычисляется в true, которое в свою очередь вычисляется в (bool)true, таким образом возвращая истинную ветвь второго тернарного выражения.

Поэтому если увлекаетесь вложенными тернарными операторами (а я сам один из тех кто любит вложенные тернарные операторы) — то совет: обрамляйте логические блоки явно круглыми скобками, дабы не запутаться. Ну а если у вас корпоративная команда и вас очень много в проекте, то лучше и вовсе все писать явно длинно, используя классические блоки условий. Выглядит, как по мне, не так красиво, зато точно понятно.

Сокращенная запись тернарного оператора

Ранее часто в работе можно было видеть такие куски кода:

<?php declare(strict_types=1);
$getvar_a = $_GET['a'] ? $_GET['a'] : 'default';

Дабы сократить дублирование кода было решено, что можно убирать дублирующую запись и с версии 5.3 можно стало писать так:

<?php declare(strict_types=1);
$getvar_a = $_GET['a'] ?: 'default';

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

Вернемся к задачке с собеседования выше. Немного переформулируем ее так:

<?php declare(strict_types=1);
// 1:
var_dump( true ? true : 1 ? 1 : 'false' );
// 2: короткая запись
var_dump( true ?: 1 ?: 'false' );

Что же в итоге будет в первом и втором варианте? Ну вы уже понимаете, что ответ 1го варианта: 1. Логично предположить, что раз короткая запись это эквивалент, то второй вариант будет тоже 1, да? Но это не точно…

В короткой нотации будет ответ true. Добавим скобок, чтобы было понятен процесс обработки:

// 1:
var_dump( (true ? true : 1) ? 1 : 'false' );
// 2: короткая запись
var_dump( true ?: (1 ?: 'false') );

Имейте это в виду, если увлекаетесь вложенными тернарниками 😉

Возвращаясь к примеру с $_GET, если вдруг $_GET['a'] не будет существовать то мы получим предупреждение:

Notice: Undefined index: a in ...

Чтобы этого не было нужно было бы предварительно еще проверить наличие переменной или индекса. И вот чтобы это тоже сократить с версии 7 был добавлен null coalescing оператор.

Null coalescing оператор

Это еще одна разновидность тернарника, но с проверкой на существование (проверкой на null).

$getvar_a = $_GET['a'] ?: 'default';
// Notice: Undefined index: a in ...
// $getvar_a = 'default'

В то время как:

$getvar_a = $_GET['a'] ?? 'default';
// all ok
// $getvar_a = 'default'
// эквивалент
$getvar_a = (isset($_GET['a']) && $_GET['a']) ?: 'default';

В принципе нечего тут объяснять, все просто и понятно. Запомнили и поехали дальше.

Наша задачка с модификациями:

<?php declare(strict_types=1);
var_dump( true ?? 1 ?? 'false' );

Отработает так же как и в случае с короткой записью тернарного оператора.

Замечание

И на закуску еще одно важное замечание: все эти операторы трактуются как выражения, и их результат — это результат выражения. Это важно знать, если вы хотите вернуть переменную по ссылке:

<?php declare(strict_types=1);
$obj1 = f();
var_dump( $obj1 );
// Notice: Only variable references should be returned by reference in ...
function &f(): ?object {
static $obj;
if (!$obj) $obj = (object) ['foo' => 123];
//return $obj; // ok
return $obj ?: null; // notice
}