16

(PHP has || and OR. JS only has ||.)

JS. According to MDN || has higher precedence than =. So this doesn't work:

a || a = 1;

because it's evaluated as:

(a || a) = 1;

which results in an "Invalid left-hand side in assignment". I understand that. That makes sense.

PHP. According to PHP.net it works the same for PHP: || before =. However, I use this all the time:

$a || $a = 1;

Why does it work in PHP?? And to top it off: PHP's OR has lower precedence than =, so these shouldn't do the same:

$a || $a = 1;
$a OR $a = 1;

but they do... https://3v4l.org/UWXMd

I think JS' || works according to MDN's table, and PHP's OR works like PHP's table, but PHP's || shouldn't work like it does.

Is this yet another weird PHP quirk?

The manual also mentions this:

Although = has a lower precedence than most other operators, PHP will still allow expressions similar to the following: if (!$a = foo()), in which case the return value of foo() is put into $a.

The precedence table dictates PHP should evaluate (!$a) = foo(), which makes no sense and should fail, but PHP evaluates it as !($a = foo()), because it loves exceptions.

Follow-up question: What do you think if ( $d = $c && $e = $b && $f = $a ) does? https://3v4l.org/3P2hN I don't get it... I do understand the second and third case (with and), just not what happens in the first.

user2864740
  • 60,010
  • 15
  • 145
  • 220
Rudie
  • 52,220
  • 42
  • 131
  • 173
  • 1
    PHP manual also say Operator precedence and associativity only determine how expressions are grouped, they do not specify an order of evaluation. PHP does not (in the general case) specify in which order an expression is evaluated and code that assumes a specific order of evaluation should be avoided, because the behavior can change between versions of PHP or depending on the surrounding code. – CY5 Sep 19 '15 at 19:01
  • @CY5 What's the difference between grouping and evaluating? What does grouping do without the evaluation order? – Rudie Sep 19 '15 at 19:06
  • PHP != JS. There is your answer. – Daniel A. White Sep 19 '15 at 19:10
  • @CY5 Who said anything about evaluation order? – melpomene Sep 19 '15 at 19:10
  • 1
    Also, I think this would be good to include in the question: `($a || $a) = 1` is a Parse Error in PHP as well. – user2864740 Sep 19 '15 at 19:39
  • 1
    Although I've not found a "good" answer yet wrt the points: http://stackoverflow.com/questions/21596754/precedence-operator-or-and-in-php?rq=1 , http://stackoverflow.com/questions/15835817/operators-precedence – user2864740 Sep 19 '15 at 19:42
  • 1
    @Stevish Ruby has `a = b unless a`, which is a logically equivalent syntax but 'designed for' the case. – user2864740 Sep 19 '15 at 19:45
  • Thanks user... I just deleted my comment when I read from an answer below which answered my question – Stevish Sep 19 '15 at 19:46
  • 1
    While not a duplicate per se, http://stackoverflow.com/a/15144605/2864740 is the best answer I've found wrt the variable-assignment production. – user2864740 Sep 20 '15 at 02:39
  • @user2864740 Yes, it is! Relevant question too! Didn't find that one. Thanks. Key is "Thus PHP parses the expression in the only possible way, ..". So it's PHP magic, bending the precedence rules to avoid parse errors. – Rudie Sep 20 '15 at 09:58
  • @Rudie Well, it's not PHP 'magic' exactly .. it's just YACC following the PHP grammar rules. Might be unexpected, but it is a valid unambiguous grammar. – user2864740 Sep 21 '15 at 05:20
  • @user2864740 Might something like this be open to interpretation by another PHP engine, HHVM for instance? I can't find the rules. If there aren't any specific enough, I'd think `$a || $a = 9;` is an error, but HHVM accepts it the same as PHP. Know if there are any compiler rules? – Rudie Sep 21 '15 at 17:28
  • @Rudie PHP only has a Zend 'reference implementation', not a formal specification - and thus *the `zend_language_parser.y` file describes the official and correct parsing rules* (and the rules are defined by a well-established parsing tool language, not a wonky manual parser). A deviation from the Zend engine would not be a compatible PHP engine. In any case, `$a || $a = 9` *isn't* an error, and has a well-defined interpretation, as it meets the well-defined (if not surprising and PHP-ish) grammar productions. – user2864740 Sep 21 '15 at 17:50
  • @Rudie While I'm not a big fan of the grammar myself, it is the *documentation* that should be updated with more technical details on the productions to avoid confusion. But the parsing (and effects of such) observed *are* the correct behavior - per being defined as such in the reference implementation aka de factor standard - despite such not being as clearly documented as might be warranted. (It would be impractical for future PHP versions to change those particular deeply-rooted parsing rules as it could break far too much code: it is what it is.) – user2864740 Sep 21 '15 at 17:53

3 Answers3

13

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 "=" expr
variable "||" expr_without_variable
expr "||" expr
expr

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.

melpomene
  • 84,125
  • 8
  • 85
  • 148
user2864740
  • 60,010
  • 15
  • 145
  • 220
  • 1
    According to that grammar, `=` isn't a real infix operator: No matter what's to the left of it, it only grabs a variable. It behaves like a meta-prefix operator: `$foo =` parses like a prefix operator for all variables `$foo`. – melpomene Sep 19 '15 at 21:02
  • @melpomene That's the crux of it: the left-hand of an `=` is restricted to a variable (as opposed to an expr) production. – user2864740 Sep 19 '15 at 22:32
  • So PHP and JS have very similar operator precedence rules, only different parsing rules? That makes sense. I was too hung up on the precedence table. Thanks! – Rudie Sep 20 '15 at 10:01
  • @Rudie The devils' be in them details :} – user2864740 Sep 20 '15 at 10:20
1

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.

Stevish
  • 734
  • 5
  • 17
  • 1
    Why is it parsed like that? According to the precedence table, it should be `$d = (($c && $e) = (($b && $f) = $a))`. – melpomene Sep 19 '15 at 19:24
  • My guess is the same reason that `"1" + 1 + 1.5` evaluates to 3.5 rather than throwing errors about trying to add a string and an integer and a float. PHP is a scripting language and (they tell me) relatively forgiving as a result. PHP doesn't evaluate the && before the = because it's in an if statement and that would make no sense (or less sense). In fact, it won't even evaluate the second or third options if the first evaluates false (which is why in your example $e and $f remain -1). If you're that far into exact order of evaluation, I suggest relying on parentheses: they're more reliable. – Stevish Sep 19 '15 at 19:33
  • I edited my cod block to more accurately reflect how PHP handles `=` and `&&` in an if condition. – Stevish Sep 19 '15 at 19:37
  • Perhaps you could say that in an if condition, PHP looks first at the &&'s and ||'s, knowing that those are used for splitting up the condition into separate possibilities. I'm just brainstorming this all, I can understand your frustration: a forgiving language is hard to figure out in an exact way. I personally rely on practices that should be unnecessary (like parentheses all over the place) in order to cut down on this frustration. – Stevish Sep 19 '15 at 19:40
  • 1
    @Stevish How do you **know** it's the same? (Besides looking at the result, which is never a good way to find out how something works.) It should be parsed like a parse error. Is this PHP trying to be extra smart and easy, doing weird things to avoid parse errors? – Rudie Sep 19 '15 at 20:16
  • 1
    If I'm reading the grammar right, this actually parses as `$d = ($c && ($e = ($b && ($f = $a))))`. – melpomene Sep 19 '15 at 21:07
-1

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.

dotancohen
  • 30,064
  • 36
  • 138
  • 197