PHP Ternary Operator: Unparenthesized
Today a colleague of mine pointed out a problem on a PHP application:
a ? b : c ? d : e is deprecated
PHP Error : Unparenthesized `a ? b : c ? d : e` is deprecated. Use either `(a ? b : c) ? d : e` or `a ? b : (c ? d : e)`
Thankfully, the error was indicating also the line number, so it was a 30-seconds fix.
But what was the problem?
I decided to dig a little more, and find out that PHP
7.4(and may be something between
7.4, I cannot confirm) deprecates the usage of *cascading ternary operators.
**I do love ternary operator, it simply allows me to compact
rvalues in pretty much any language I use (and in Perl also to
lvalueit, but hey, not many languages are great as Perl is…).
I do love ternary operator so much that I started also to cascade them, that means to use a ternary operator as a branch of another ternary operator. So far so good, unless I discovered that PHP does not support very well this feature.
Or better, it does support, but right-to-left, that not only is counterintuitive, but also different from pretty much any other language. The latter means that what you are expecting to work in any decent language is not going to work that way in PHP.
Allow me to demonstrate with a quick example:
use v5.20; my $a = 10; my $b = 20; my $c = 30; my $d = 40; my $e = 50; say "With all set should print b = $b\n"; say $a ? $b : $c ? $d : $e;
What is the above going to print?
$ais set, so the first branch is selected, therefore
% perl test.pl With all set should print b = 20 20
The result is the same in Raku:
% raku test.p6 With all set should print b = 20 20 % cat test.p6 #!raku my $a = 10; my $b = 20; my $c = 30; my $d = 40; my $e = 50; say "With all set should print b = $b\n"; say $a ?? $b !! $c ?? $d !! $e;
So, the question is: what about PHP?
Surprisingly, it works in a very different way:
% php test.php With all set should print b = 20 40 <?php $a = 10; $b = 20; $c = 30; $d = 40; $e = 50; echo "With all set should print b = $b\n"; echo isset( $a ) ? $b : isset( $c ) ? $d : $e;
What is happening here? In PHP, due to the design of the parser, the ternary operator is executed from right to left, or better, the inner operator has the precedence and therefore it reads as
isset( $c ) ? $d : $eand then the
isset( $a ) ? $bbranch is executed.
Allow me to explain with another example:
% php test.php With all set should print b = 20 20 b = 21 d = 40 e = 50 % cat test.php <?php $a = 10; $b = 20; $c = 30; $d = 40; $e = 50; echo "With all set should print b = $b\n"; echo isset( $a ) ? $b : ( isset( $c ) ? $d : $e ); isset( $a ) ? ++$b : ( isset( $c ) ? ++$d : ++$e ); echo "\n b = $b d = $d e = $e"; ?>
As you can see, with parentheses the parser is able to understand that the first colon term is something apart from the outer expression, and therefore will be evaluated only when effectively needed.
% php test.php b = 21 d = 41 e = 50
And therefore both true branches have been executed, even if the
isset( $c )should have been totally short-circuited.
PHP 7.4 and possible solutionThe above behavior is not a result of the
7.4version, it has been there since day one!
Wikipedia states it loudly:
Due to an unfortunate design of the language grammar, the conditional operator in PHP is left associative in contrast to other languages [..]
PHP 7.4 has only added a deprecation to this unwanted behavior, in order to force you to write it down the right way, that is using parenthesis to explicitly tell the priority:
ConclusionsI don’t like PHP that much, and this even before I get to know the above dizzy feature. I think it would have been better to remove that behavior when it was still possible, but I could be wrong.
So the real conclusion is that sometimes the parser can get a very hard time in trying to figure out what you are trying to do. And this is true for every programming language.