The ternary operator execution order

    There is an interesting feature of ternary operator in php - a specific and unique order of execution.

    $ 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 and C ++ will also return 1


    What's the difference?


    I know about this interesting nuance for a long time, but just yesterday I discovered an error in one of the open source sources: the author clearly did not know about this nuance and got caught. Therefore, this article is just a warning. After all, if a programmer expects the same behavior from php as from other languages, he could fall into a crap.

    This technique is very convenient for setting values, depending on the conditions. Elegant if-else replacement. For instance:
    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();
    }
    


    How to avoid a mistake?


    The first way is not to use the ternary operator in php.
    The second is to directly indicate the order of execution using parentheses:

    $ php -r "print true ? 1 : (true ? 2 : (true ? 3 : (true ? 4 : 5)));"
     1

    Something reminiscent of Lisp, right?

    Why does it even happen this way?


    Let's look at the execution order of the ternary operator using JavaScript vs PHP as an example.
    We will write two test scripts to understand how each language works.

    Just in case, I’ll explain that when $foo = $lambda('fooMsg', 'fooReturn'), $foocontains a function that, when called, displays a message in the console 'fooMsg'and returns a value'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
     $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


    Which of this result can be concluded. Javascript parses the ternary operator quite logically. First checks the leftmost condition. If it is true, then it executes and returns the left part after the first colon, if not true, then the right.
    (cond.first() ? value.first() :
    	(cond.second() ? value.second() :
    		(cond.third() ? value.third() :
    			(value.default()))));
    /********
     * ===> 
     */
    true ? 'value.first' : /* ignored */;
    


    PHP thinks original.

    (
    	(
    		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'
    


    What is confirmed in the official manual :
    $a = true ? 0 : true ? 1 : 2; // (true ? 0 : true) ? 1 : 2 = 2
    


    Conclusion


    In general, PHP showed itself again “from the best side”, but the problem is far from critical. The main thing is to know about it and be careful with the ternary operator in PHP.

    Also popular now: