JS vs PHP: assignment operator precedence when used with logical-or JS vs PHP: assignment operator precedence when used with logical-or php php

JS vs PHP: assignment operator precedence when used with logical-or


According to zend_language_parser.y the code is parsed equivalently to $a || ($a = 1) and $a or ($a = 1) in each case, respectively.

As summarized by melpomene, the assignment productions are not infix binary operators over expressions; rather assignment operators are restricted productions where the left-hand side must be a variable production.

Per a borrowed quote:

Thus PHP parses the expression in the only possible way..

The documentation is correct about the precedence .. where it applies.


Thus $a || $a = 1 follows the (reversed) productions of:

variable "||" variable "=" exprvariable "||" expr_without_variableexpr "||" exprexpr

The case of !$a = foo() is similar and is parsed as !($a = foo()) after following the (reversed) productions:

"!" variable "=" expr"!" expr_without_variable"!" expr                 expr

Now, how about $d = $c && $e = $b && $f = $a? It is not parsed as ($d = $c) && .. even though the && does have a higher precedence than the assignment. It is actually parsed as $d = ($c && ($e = ..)) and so on, to be completed by the astute reader.

While it might not be casually noticed, this difference is capable of producing varying results:

$a = (($c = 1) && ($d = 0));var_dump($a, $c, $d);         // => false, 1, 0$b = ($e = 1 && $f = 0);      // => $b = ($e = (1 && ($f = 0)));var_dump($b, $e, $f);         // => false, false, 0

Parenthesis should thus generally be used when mixing assignment operators with operators of higher precedence, especially when the result of such may be .. unclear.

As inconsistent as this may initially seem, it is a well-defined grammar - but the technical details are buried behind some fairly layman documentation; and the rules differ subtly from those in other C-syntax-like languages. The lack of an official EBNF in the documentation doesn't help.


Despite the parsing details, the $a || $a = .. code (which is valid and well-defined syntax) should remain well-defined from an evaluation viewpoint as the left side of the 'or' must occur prior to the right due to guaranteed short-circuiting.


For contrast, in JavaScript, a || a = 1 is parsed as (a || a) = 1 - which is also syntactically 'valid' code - per the ECMAScript Grammar Rules. However, a || a does not yield a valid Reference Specification Type and thus a runtime ReferenceError is thrown.


As to your followup question: if ( $d = $c && $e = $b && $f = $a ) is the same as:

$d = $c;if($d) {    $e = $b;    if($e) {        $f = $a;        if($f) {            ...        }    }}

I assume you know this, but some of the questions are confusing to me, so I'll mention it... = is an assignment operator, not a comparison operator. if($a = $b) doesn't check if $a and $b are the same, it makes $a equal $b, then checks if $a evaluates to true. if($a == $b) checks if the two variables are the same.


The expression $a || $a = 1; is equivalent to this:

if ( $a != true ) {    $a = 1;}

A very common variant on the idea is used for poor-mans debugging:

$debug = true;// Thousands of lines of code$debug && printf("Foo: {$foo}");// More code$debug && printf("Bar: {$bar}");

In this paradigm, only the $debug statement needs to be set to true/false to enable/disable debugging. I'm not advocating this type of debugging, but I've seen it quite a few times.