5

I am fairly capable at using the PHP ternary operator. However I have hit a roadblock at trying to figure out why the code below does not match the if-else equivalent structure. The test was run three times on different numbers. The output for each structure is below the code.

Ternary:

$decimal_places = ($max <= 1) ? 2 : ($max > 3) ? 0 : 1;

Ternary Output:

max: -100000 decimal: 0

max: 0.48 decimal: 0

max: 0.15 decimal: 0

If-Else

if($max <= 1)
 $decimal_places = 2;
elseif($max > 3)
 $decimal_places = 0;
else
 $decimal_places = 1;

If-Else Output:

max: -100000 decimal: 2

max: 0.48 decimal: 2

max: 0.15 decimal: 2

Can anyone please tell me why the these two control stuctures do not output the same data?

Community
  • 1
  • 1
Patrick
  • 3,142
  • 4
  • 31
  • 46
  • 4
    One reason why it isn't a good idea to nest ternary operators – Mark Baker Jan 26 '11 at 17:04
  • 1
    They're not exact equivalents, you know. The entire elseif will short-circuit in the ternary structure. They're also not meant to be nested. – Rafe Kettler Jan 26 '11 at 17:05
  • 1
    [From the PHP Manual on Ternary Operators](http://de3.php.net/manual/en/language.operators.comparison.php#language.operators.comparison.ternary): It is recommended that you avoid "stacking" ternary expressions. PHP's behaviour when using more than one ternary operator within a single statement is non-obvious – Gordon Jan 26 '11 at 17:12
  • @Mark Baker I've only tried to nest them on very simple statements since I have seen ternary statements that make you want to do something violent. – Patrick Jan 26 '11 at 17:23
  • ALL usages of the ternary operator make me want to do something violent. There is zero good reason to add them. Humans have to read code far more than they have to write it. Making it "concise" with terrible constructs like the ternary operators adds bugs and decreases maintainability as you've seen with your question. – taiganaut Nov 02 '12 at 21:26

4 Answers4

18

Your right-hand-side ternary expression needs to be wrapped in parentheses so it'll be evaluated by itself as a single expression:

$decimal_places = ($max <= 1) ? 2 : (($max > 3) ? 0 : 1);

// Another way of looking at it
$decimal_places = ($max <= 1)
                ? 2
                : (($max > 3) ? 0 : 1);

Otherwise your ternary expression is evaluated from left to right, resulting in:

$decimal_places = (($max <= 1) ? 2 : ($max > 3)) ? 0 : 1;

// Another way of looking at it
$decimal_places = (($max <= 1) ? 2 : ($max > 3))
                ? 0
                : 1;

Which, translated to if-else, becomes this:

if ($max <= 1)
    $cond = 2;
else
    $cond = ($max > 3);

if ($cond)
    $decimal_places = 0;
else
    $decimal_places = 1;

Therefore $decimal_places ends up as 0 for all values of $max except 2, in which case it evaluates to 1.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
2

The code is executed as

$decimal_places = (($max <= 1) ? 2 : ($max > 3)) ? 0 : 1;

so you'll never get 2 and 1 only when 1 < $max <=3. This is because the conditional operator is left-associative. Solution: Place parentheses to make sure the order you want is coded:

$decimal_places = ($max <= 1) ? 2 : (($max > 3) ? 0 : 1);
phihag
  • 278,196
  • 72
  • 453
  • 469
1

As others pointed out, use paranthesis.
However, if you actually want to make it readable, what about this:

$decimal_places =
  ($max <= 1) ? 2 : (
  ($max > 3)  ? 0 : (
  1
));

This still looks super awkward, but this awkwardness has a regular shape, so it's easier to live with.

$drink = 'wine';
return
  ($drink === 'wine')   ? 'vinyard' : (
  ($drink === 'beer')   ? 'brewery' : (
  ($drink === 'juice')  ? 'apple tree' : (
  ($drink === 'coffee') ? 'coffeebeans' : (
  'other'
))));

You could of course omit the last pair of brackets, but that would make it less regular-looking.

donquixote
  • 4,877
  • 3
  • 31
  • 54
  • Super awkward indeed, but still I got that going for me which is nice (because i got lots of controls if statements inside switches etc...) – FreshPro Mar 03 '14 at 11:58
1

Just put the parenthesis and you would be fine, like this:

 $decimal_places = ($max <= 1) ? 2 : (($max > 3) ? 0 : 1);
shamittomar
  • 46,210
  • 12
  • 74
  • 78