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.

14
  • 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. Commented Sep 19, 2015 at 19:01
  • 1
    Also, I think this would be good to include in the question: ($a || $a) = 1 is a Parse Error in PHP as well. Commented Sep 19, 2015 at 19:39
  • 1
    Although I've not found a "good" answer yet wrt the points: stackoverflow.com/questions/21596754/… , stackoverflow.com/questions/15835817/operators-precedence Commented Sep 19, 2015 at 19:42
  • 1
    @Stevish Ruby has a = b unless a, which is a logically equivalent syntax but 'designed for' the case. Commented Sep 19, 2015 at 19:45
  • 1
    While not a duplicate per se, stackoverflow.com/a/15144605/2864740 is the best answer I've found wrt the variable-assignment production. Commented Sep 20, 2015 at 2:39

3 Answers 3

12

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.

Sign up to request clarification or add additional context in comments.

4 Comments

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 That's the crux of it: the left-hand of an = is restricted to a variable (as opposed to an expr) production.
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 The devils' be in them details :}
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.

6 Comments

Why is it parsed like that? According to the precedence table, it should be $d = (($c && $e) = (($b && $f) = $a)).
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.
I edited my cod block to more accurately reflect how PHP handles = and && in an if condition.
@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?
If I'm reading the grammar right, this actually parses as $d = ($c && ($e = ($b && ($f = $a)))).
|
-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.

3 Comments

Please respond to the OPs points: which indicate it is not parsed like that. Also, || is not the same as && logically.
(Well, "should not be" rather.)
Your very first code block is wrong: if ( $a == true ) {. You mean false. And I know this. Does not answer my question at all though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.