$ python -c "print 1 if true else 2 if true else 3 if true else 4 if true else 5"
1
$ node -e " true ? 1 : true ? 2 : true ? 3 : true ? 4 : 5"
1
$ perl -e "print true ? 1 : true ? 2 : true ? 3 : true ? 4 : 5"
1
$ ruby -e "print true ? 1 : true ? 2 : true ? 3 : true ? 4 : 5"
1
$ php -r "print true ? 1 : true ? 2 : true ? 3 : true ? 4 : 5;"
4
Java и C++ тоже вернут 1А какая вообще разница?
Я знаю об этом интересном нюансе довольно давно, но буквально вчера обнаружил ошибку в одном из открытых исходников: автор явно не знал об этом нюансе и попался. Потому, данная статья служит всего-лишь предупреждением. Ведь, если программист ожидает от php такого же поведения, как от других языков — может попасть в халепу.
Такой приём очень удобный для задания значений, зависимо от условий. Изящная замена if-else. Например:
value = isCondFirst() ? valueFirst() : isCondSecond() ? valueSecond() : isCondThird() ? valueThird() : valueDefault(); /********** Вместо **********/ if (isCondFirst()) { value = valueFirst(); } else if (isCondSecond()) { value = valueSecond(); } else if (isCondThird()) { value = valueThird(); } else { value = valueDefault(); }
Как избежать ошибки?
Первый способ — не использовать тернарный оператор в php.
Второй — прямо указывать порядок выполнения с помощью скобок:
$ php -r "print true ? 1 : (true ? 2 : (true ? 3 : (true ? 4 : 5)));"
1
Чем-то напоминает Лисп, не так ли?
Почему оно вообще так происходит?
Давайте разберемся в порядке выполнения тернарного оператора на примере JavaScript vs PHP
Напишем два тестовых скрипта, чтобы понять, как работает каждый из языков.
На всякий случай поясню, что при
$foo = $lambda('fooMsg', 'fooReturn'), $foo содержит в себе функцию, которая при вызове выведет в консоль сообщение 'fooMsg' и вернет значение 'fooReturn'$ cat ternary.js
var lambda = function (logMsg, returnValue) { return function () { console.log(logMsg); return returnValue; }; }; var cond = { first : lambda('cond.first' , true), second: lambda('cond.second', true), third : lambda('cond.third' , true) }; var value = { first : lambda('value.first' , 'first'), second : lambda('value.second' , 'second'), third : lambda('value.third' , 'third'), default: lambda('value.default', 'default') }; console.log( 'result: ', cond.first() ? value.first() : cond.second() ? value.second() : cond.third() ? value.third() : value.default() );
$ node ternary.js
cond.first
value.first
result: first
$ cat ternary.php
<?php $lambda = function ($logMsg, $returnValue) { return function () use ($logMsg, $returnValue) { echo $logMsg . PHP_EOL; return $returnValue; }; }; $cond = array( 'first' => $lambda('cond.first' , true), 'second'=> $lambda('cond.second', true), 'third' => $lambda('cond.third' , true), ); $value = array( 'first' => $lambda('value.first' , 'first'), 'second' => $lambda('value.second' , 'second'), 'third' => $lambda('value.third' , 'third'), 'default'=> $lambda('value.default', 'default'), ); echo 'result: ' . ( $cond['first']() ? $value['first']() : $cond['second']() ? $value['second']() : $cond['third']() ? $value['third']() : $value['default']() ) . PHP_EOL; ?>
$ php ternary.php
cond.first
value.first
value.second
value.third
result: third
Какой из этого результата можно сделать вывод. Javascript разбирает тернарный оператор вполне логично. Сначала проверяет самое левое условие. Если оно верно, то выполняет и возвращает левую часть после первого двоеточия, если не верно, то правую.
(cond.first() ? value.first() : (cond.second() ? value.second() : (cond.third() ? value.third() : (value.default())))); /******** * ===> */ true ? 'value.first' : /* ignored */;
PHP мыслит оригинально.
( ( cond.first() ? value.first() : cond.second() ) ? value.second() : cond.third() ) ? value.third() : value.default(); /******** * ===> */ ( ( 'value.first' ) ? value.second() : cond.third() ) ? value.third() : value.default(); /******** * ===> */ ( 'value.second' ) ? value.third() : value.default(); /******** * ===> */ 'value.third'
Что подтверждается в официальном мануале:
$a = true ? 0 : true ? 1 : 2; // (true ? 0 : true) ? 1 : 2 = 2
Заключение
В общем PHP себя снова показал «с лучшей стороны», но проблема далеко не критическая. Главное — знать про неё и быть осторожным с тернарным оператором в PHP.