2

Coming from C# I must do a project in PHP.

I am using this code:

$transport = 'T';

$vehicle = (
 ( $transport == 'B' ) ? 'bus' :
 ( $transport == 'A' ) ? 'airplane' :
 ( $transport == 'T' ) ? 'train' :
 ( $transport == 'C' ) ? 'car' :
 ( $transport == 'H' ) ? 'horse' :
 'feet' );

echo $vehicle;

I would expect it to print train, but I get horse. Codepad example: http://codepad.org/rWllfrht

Who can explain this strange behaviour?

Piepje
  • 117
  • 5
  • 31
    **Don't use that code**. That is a horrible, unforgivable abuse of the ternary operator. If I ever found that code in production, I would immediately delete it and have very harsh words with the author. Use a `switch` statement. – user229044 Oct 30 '12 at 15:39
  • PHP supports *switch* http://php.net/manual/en/control-structures.switch.php – Eric J. Oct 30 '12 at 15:40
  • How about using a [switch](http://php.net/manual/en/control-structures.switch.php) instead? – Christofer Eliasson Oct 30 '12 at 15:40
  • 14
    This code is taken almost verbatim from the wikipedia page at http://en.wikipedia.org/wiki/%3F:#PHP which explains what is going on. I call troll :p – Mat Oct 30 '12 at 15:41
  • 1
    Speaking of C#, it had a [bug](http://stackoverflow.com/questions/6256847/curious-null-coalescing-operator-custom-implicit-conversion-behaviour) in a similar situation. – Bakuriu Oct 30 '12 at 15:43
  • why would you use ternary statements when you could simplify your statement with if or switch? – Christopher Pelayo Oct 30 '12 at 15:43
  • I switch would be much longer. This code works perfect in C#. Is this a bug in PHP? It seems I touched a open nerve? – Piepje Oct 30 '12 at 15:43
  • 3
    Ugliest, most unreadable ternary usage I have had the displeasure of seeing. It's relying on a weird cascade of association that I would hate to debug. As others have said, use a switch. – lynks Oct 30 '12 at 15:43
  • 2
    oooo! Look at all the pretty downvotes! Wiki users must be angry that there was no attribution given :P – Lix Oct 30 '12 at 15:44
  • [To ternary or not to ternary](http://stackoverflow.com/questions/160218/to-ternary-or-not-to-ternary) – Kermit Oct 30 '12 at 15:44
  • Not a bug. A difference in how precedence works. The manual explicitly explains why you shouldn't do this. – user229044 Oct 30 '12 at 15:47
  • 2
    From the wiki I read it is a ackowleged bug. I still do not 100% get it. What a nice site is this. Valid question, 3 downvotes, 21 rants **do not use that**, no explanation. Welcome to Stackoverflow! – Piepje Oct 30 '12 at 15:52
  • 2
    From the wiki? Then you should have seen **the explanation right beneath it**. – Mansfield Oct 30 '12 at 15:53
  • @use - You fell on a slightly controversial question here. Please don't let one experience dictate your views on the site. Give it a fair chance - I'm sure you will see the better parts! – Lix Oct 30 '12 at 15:54
  • 1
    @user1782842 But you're not a new user. Why did you create a new account to post this? – NullUserException Oct 30 '12 at 15:55
  • 2
    Had you outlined that you read it from the wiki (etc) but wanted some clarification may have provided the feedback wanted... the way you posed the question was not asking this :) – Brian Oct 30 '12 at 15:56
  • @Brian: I did not read it from the wiki, and it is not my original code. I used a shorter version, but in essence the same. I hoped to get a answer from the pro's here. Instead I got 5 downvotes and the question is closed as to localized. It says all about what SO has become. Rushing to quick answers, without a eye for real programming questions. – Piepje Oct 30 '12 at 16:03
  • 1
    What was posted was verbatim... – Brian Oct 30 '12 at 16:04
  • @pie - One could also say the same thing about judging the entire community of [so] after being a member for only 2 days and only having one post... (just saying). – Lix Oct 30 '12 at 16:47
  • The example in the question *is* too trivial and can be achieved by other means, but [there is a case](http://codepad.org/4wPoCq1Z) where such pattern would be valid. – transistor09 Mar 26 '15 at 21:57
  • [PHP nested conditional operator bug?](https://stackoverflow.com/q/1921422/995714) – phuclv Aug 13 '17 at 11:39

6 Answers6

16

Not seeing any explanation about why your code is broken in the other answers, so here is a quick run-down.

The problem here is made more obvious is you add brackets to make the implicit order of evaluation more explicit.

Here's a trimmed down version of your code, which still produces the incorrect result of "horse":

 $t = 'T';

 ( $t == 'T' ) ? 'train' : 
 ( $t == 'C' ) ? 'car' : 
 ( $t == 'H' ) ? 'horse' : 'feet';

First, lets unroll it:

( $t == 'T' ) ? 'train' : ( $t == 'C' ) ? 'car' : ( $t == 'H' ) ? 'horse' : 'feet'; 

Next, I'll add explicit parenthesis where there are already implicit ones:

((($t == 'T') ? 'train' : ($t == 'C')) ? 'car' : ($t == 'H')) ? 'horse' : 'feet';

Next, we can resolve your comparisons:

((true ? 'train' : false) ? 'car' : false) ? 'horse' : 'feet';

You should start to see why this is broken. The first ternary evaluates true ? 'train' : 'false' to 'train':

('train' ? 'car' : false) ? 'horse' : 'feet';

Because 'train' is true when cast to a boolean, the result is now 'car':

'car' ? 'horse' : 'feet';

Again, because a non-empty string is "true", the result is now 'horse'. So, the first time a true comes up in your horrible nested case statement, the result will cascade through all remaining statements, throwing out the previous value for the "true" branch of the next operator.

The solution is to avoid this code. It is an attempt to be far, far too clever, and the result is a broken, unreadable mess. There is absolutely no reason to use it. Choose a switch statement, it's purpose built for exactly what you're trying to do.

NullUserException
  • 83,810
  • 28
  • 209
  • 234
user229044
  • 232,980
  • 40
  • 330
  • 338
  • 3
    Works fine in any sensible language ([Java](http://ideone.com/dde4pt), [C#](http://ideone.com/QF62Bh), [C++](http://ideone.com/Z2b8lJ)). A documented bug does not a feature make. – NullUserException Oct 30 '12 at 16:19
  • For good measure, here is [JavaScript](http://ideone.com/PUS7EQ), [Perl](http://ideone.com/I10VNu), and [Ruby](http://ideone.com/gv8IU2). I have yet to find a language that does it the "PHP way." Still, +1 for explaining what is going on here. – NullUserException Oct 30 '12 at 16:31
  • The "PHP way" is an acknowledged bug which still exists for backwards-compatibility, even across major releases. I wouldn't expect to find it in any other language which was written by *sane authors*. – user229044 Oct 30 '12 at 18:02
  • 2
    It's confusing in any language, and just about any language will provide better constructs designed to do this in a clear way. – Chris Miller Oct 30 '12 at 19:03
  • meager, you said it wasn't a bug in a comment under the question. I believe it is a bug and not fixed for backwards compatibility like you said there. @ChrisMiller I'm not arguing there aren't better ways to do this in any language, just that the way PHP handles it is very counter-intuitive. – NullUserException Oct 30 '12 at 19:22
  • @NullUserException I'm not arguing that it's not a bug not fixed for backwards compatibility. Which happens to make it very counter-intuitive. :) – Chris Miller Oct 30 '12 at 19:37
  • @meagar: Thanks! Your solution explained why mine did not work. See http://i.imgur.com/PH2ZEc6.png –  May 07 '14 at 14:26
  • I think it's unfair to say it's a bug. Someone screwed up when defining the ternary operator's associativity. They made a mistake, but it's not a bug, it's well-defined (albeit unideal) behaviour. – Andrea Jan 07 '15 at 04:29
  • @AndreaFaulds err, what exactly do you think a bug *is*? – user229044 Jan 07 '15 at 04:32
  • @meager Erroneous, unintended behaviour. But there is a difference between language specification errata and a bug. Something may have been a poor decision, but there's nothing wrong with the PHP interpreter, well, interpreting the PHP language correctly. A mistake was made and the ternary operator was defined the wrong way. But it's not a bug in PHP. It is merely an unfortunate part of the language. – Andrea Jan 07 '15 at 04:39
  • @AndreaFaulds I would call PHPs handling of nested ternaries "erroneous unintended behavior". That fits perfectly. – user229044 Jan 07 '15 at 04:41
3

This doesn't work as expected due to a bug in the PHP language grammar, as seen at: http://en.wikipedia.org/wiki/%3F:#PHP

Here's a simple version that DOES work:

$transport = 'T';

$vehicle = (
 ( $transport == 'B' ? 'bus' :
 ( $transport == 'A' ? 'airplane' :
 ( $transport == 'T'  ? 'train' :
 ( $transport == 'C'  ? 'car' :
 ( $transport == 'H'  ? 'horse' :
 'feet' ))))));

echo $vehicle;

But as everyone else said, I agree this isn't the best way to do this. You could use a switch case, if else if, or associative array and be a lot more readable.

Chris Miller
  • 493
  • 2
  • 10
1

This is a sort of "working-as-intended-even-though-it's-clearly-wrong" behaviour of PHP. It doesn't associate that way, so while this code works in most other languages, it will fail in PHP. Lesson? Learn to use parenthesis over unusual association paradigms. Lesson Two? Ternary isn't a magic bullet, while it can be nice and compact, it should only be used when it's readable. IMHO nested ternary statements are just ugly.

lynks
  • 5,599
  • 6
  • 23
  • 42
0

I'm not sure exactly why you chose to use this form of syntax, as mentioned in a comment, this would be a nightmare to debug... A switch case might be a better choice here -

$vehicle = '';
switch($transport){
  case 'B' :
    $vehicle = 'bus';
  break;
  case 'A' :
    $vehicle = 'airplane';
  break;
  ...
  default:
   // undefined cases
  break;
}

References -

Community
  • 1
  • 1
Lix
  • 47,311
  • 12
  • 103
  • 131
0

Learn to love paranthesis if you want to do something like this:

$vehicle =     ( ( $transport == 'B' ) ? 'bus' : 
                    (( $transport == 'A' ) ? 'airplane' :
                       (( $transport == 'T' ) ? 'train' :
                         (( $transport == 'C' ) ? 'car' :
                           (( $transport == 'H' ) ? 'horse' :'feet')))) );

Each right hand side of the ternary, needs to be clearly contained because of PHP's order of operations for ternary http://php.net/manual/en/language.operators.comparison.php.

By the way, from that page they explicitly recommend against stacking them like this...

Note: 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:

Ray
  • 40,256
  • 21
  • 101
  • 138
  • 1
    @jackflash not saying it's good, but it makes the awful ternary work. – Ray Oct 30 '12 at 15:46
  • The only answer which points to the actual problem, even though you could explain a bit what happens in the expression without the parenthesis. – Bakuriu Oct 30 '12 at 15:47
  • @user1782842 my original answer forgot a paranthesis, it's now fixed. – Ray Oct 30 '12 at 15:47
  • @Ray That's what happen when doing that... you forget parenthesis :) Anyway, OP is just trolling us. – Carlos Oct 30 '12 at 15:51
-1

I won't advise you to use such code but for educational purposes it should be

$transport = 'T';
$vehicle = (
        ($transport == 'B') ? 'bus' : 
            (($transport == 'A') ? 'airplane' : 
                (($transport == 'T') ? 'train' : 
                        (($transport == 'C') ? 'car' : 
                                (($transport == 'H') ? 'horse' : 'feet'))))
        );

echo $vehicle;

A better code should be

$transport = 'T';
switch ($transport) {
    case 'A' :
        $vehicle = 'airplane';
        break;
    case 'B' :
        $vehicle = 'bus';
        break;
    case 'C' :
        $vehicle = 'car';
        break;
    case 'H' :
        $vehicle = 'horse';
        break;
    case 'T' :
        $vehicle = 'train';
        break;
    default :
        $vehicle = 'teleportation';
        break;
}

echo $vehicle;

Or better still:

$transport = 'T';
$array = array('A'=>'airplane','B'=>"bus","C"=>"car","H"=>"horse","T"=>"train");
echo isset($array[$transport]) ? $array[$transport] : null;

Or, use a database:

 SELECT name FROM transpotationTable WHERE someKey = '$transport' 
NullUserException
  • 83,810
  • 28
  • 209
  • 234
Baba
  • 94,024
  • 28
  • 166
  • 217
  • 5
    In PHP 7, you could even write this: `['A'=>'airplane','B'=>"bus","C"=>"car","H"=>"horse","T"=>"train"][$transport] ?? null` – Andrea Jan 07 '15 at 04:27
  • Ohhh @Andrea. That is *nice*. – TRiG Feb 05 '16 at 13:09
  • Or for the OP's case: `['A'=>'airplane','B'=>"bus","C"=>"car","H"=>"horse","T"=>"train"][$transport] ?? "feet"` – Eljay Feb 22 '18 at 14:33