logical vs assignment operator precedence in PHP - php

Recently i came upon such snippet:
$x = 2 && $y = 3; echo (int)$x.':'.(int)$y;
Which produces output 1:3.
By looking at operator precedence sheet i see that logical operators || and && has higher precedence than assignment operator =. So first expression should be evaluated as $x = (2 && $y) = 3; which becomes $x = (2 && null) = 3; and finally evaluates to $x = false = 3; Secondly - assignment operator has right associativity, so interpreter should try to execute false = 3 which is illegal of course. So in my opinion above mentioned code snippet should not compile at all and must throw parse or run-time error. But instead of that script produces 1:3. Which means that interpreter executed actions are:
a) $y=3
b) 2 && $y
c) $x = (2 && $y)
Why it is so and not according to operator precedence ?

The operator precedence sheet you link to states as a separate note:
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.
So, in effect, an assignment inside an expression will be treated somewhat like a sub-expression. Exactly how and when this will happen isn't clear from the documentation, which just states that "similar" expressions will work this way.

Related

Have I fundamentally misunderstood how PHP/C-like programming languages work? [duplicate]

This question already has answers here:
logical vs assignment operator precedence in PHP
(1 answer)
'AND' vs '&&' as operator
(10 answers)
PHP Logical Operators precedence affects variable assignment results strangely
(3 answers)
Closed 3 years ago.
Take a look at this code:
if ($the_string = a_function_call('someid123') && $another_string = another_function_call('someid123'))
{
// $the_string and $another_string are now both expected to be the values returned from their respective functions, and this block of code is expected to be run if both of them are not "falsey".
}
However...
if ($the_string = a_function_call('someid123') && $another_string = another_function_call('someid123'))
{
// $the_string is now actually a boolean true. Not sure about $another_string.
}
Is this a weird bug? Or the intended behaviour? I've always thought that it was like the first scenario, and I've always coded by that assumption.
&& has higher precedence than =. So your code is being parsed as if you'd written
if ($the_string = (a_function_call('someid123') && ($another_string = another_function_call('someid123'))))
This performs the following steps:
Calls a_function_call()
If that returns a truthy value, it calls another_function_call() and assigns the result to $another_string().
Assigns the truth value of the && expression to $the_string
Tests that truth value in the if statement.
This precedence allows you to write code like:
$success = function_1() && function_2();
If = had higher precedence, that would set $success just from function_1(), not the combination.
Adding parentheses would solve the problem:
if (($the_string = a_function_call('someid123'))
&& ($another_string = another_function_call('someid123')))
PHP also has and and or operators that are like && and ||, but they have lower precedence than assignment; they exist to address this issue. You often see them used in code like this:
$variable = some_function(...) or die("some_function didn't work");
So simply replacing && with and would also solve the problem.
The key is the operator precedence.
The expression is evaluated as follow (pay attention to the parentheses):
if ($the_string = (a_function_call('someid123') && $another_string = another_function_call('someid123')))
$another_string takes the value from another_function_call() than is checked against a_function_call() return value through an AND operator.
Add the correct parentheses as follow to get the expected result:
if (($the_string = a_function_call('someid123')) && ($another_string = another_function_call('someid123')))
Please check the php operators precedence.

Initializing a variable and using that in the if clause

if($y=3){echo $y;} and if($y=1 and $y>2){echo $y;} work as expected. However, I wonder why it's not possible to use this if($x=1 && $x>3){echo $x;} , which returns Notice: Undefined variable: x. (and operator has higher precedence than &&) If && first evaluates the right operand and after that evaluates the left hand shouldn't it work?
$y=1 and $y>2 works as you expected because:
As per operator precedence rule, = has higher precedence than and. So, it evaluates as:
($y = 1) $y>2
So, assignment takes place. (Expression inside parentheses evaluates first)
$x=1 && $x>3 doesn't work as you expected because:
As per operator precedence rule, && has higher precendence than =. So, it evaluates as:
$x=(1 && $x)>3
Naturally, $x is compared with 1 which is undeclared initially. So the undefined variable error pops up. So if you want to change the order, change the expression to:
($x=3) && $x>2
So your if will look like:
if(($x=3) && $x>2) {
echo $x;
}
if($y=1 and $y>2){echo $y;}
"=" assignment operator has higher precedence than "and".
"=" assignment operator initialize $y and works as expected.
if($x=1 && $x>3){echo $x;}
"=" assignment operator has lower precedence than "&&".
"&&" operator execute before assignment, hence in this case $x is not defined/initialized and which returns Notice : Undefined variable : x.

"Undefined variable" when used after assignment in an if-clause

if (true && 1 == $x = 1 && $x) {
echo "Hello world!";
}
I get an Undefined Variable Notice for $x. Shouldn't the parser know $x at this point, because it solves the If-Clause from left to right?
The observed behavior is due to how the expression is parsed. The following
if (1 == $x = 1 && $x) {
echo "Hello world!";
}
results in an "Undefined variable: x" and does not echo, but
if (1 == ($x = 1) && $x) {
echo "Hello world!";
}
"works" and emits Hello world! as expected.
This is because the former is equivalent to
if (1 == ($x = (1 && $x))) {
echo "Hello world!";
}
and the use of $x is inside the expression that will be used for the assignment. Since this is evaluated before the assignment takes effect, $x is still an "undefined variable" according to PHP.
Solutions:
Use parenthesis as shown; or,
Avoid variable assignments in conditional expressions, which I recommend.
Although operator precedence is the primary scapegoat, the first form is not equivalent to (1 == $x) = (1 && $x) which is the derivation when directly following the precedence table. If it were parsed in such a manner it would result in a "syntax error, = unexpected".
This parsing is a quirk of PHP and differs from C grammar rules. (The YACC rules for PHP can be found in zend_language_parser.y.)
PHP: 1 && $x = 2 -> 1 && ($x = 2), as per the above stated equivalency. This happens even though && is stated to have a higher precedence.
C: 1 && x = 2 -> (1 && x) = 2, invalid syntax: "lvalue required as left operand of assignment". In C, the && operator really does have higher precedence in an expression.
This same behavior is evident when replacing && with ==: despite an assignment having a lower documented priority, the simple precedence table is misleading and inaccurate when assignments are part of expressions.
In conclusion, $x = expr as part of an expression is parsed equivalently to ($x = (expr)) where expr can itself be a complex expression containing operators and other expressions.

The strange ways of the "or" in PHP

PHP's or is an weird keyword. Here it is in a code snippet that makes me confused:
echo 0 or 1; // prints 1
$foo = (0 or 1);
echo $foo; // prints 1
$foo = 0 or 1;
echo $foo; // prints 0 for some reason
Why does the last one print 0 and not 1?
This is because of different operator precedence. In the third case, the assignment is handled first. It will be interpreted like this:
($foo = 0) or 1;
The || operator has a different precedence. If you use
$foo = 0 ||1;
It will work as you expect.
See the manual on logical operators
No, I wouldn't, that's because of operator precedence:
$foo = 0 or 1;
// is same as
($foo = 0) or 1;
// because or has lower precedence than =
$foo = 0 || 1;
// is same as
$foo = (0 || 1);
// because || has higher precedence than =
// where is this useful? here:
$result = mysql_query() or die(mysql_error());
// displays error on failed mysql_query.
// I don't like it, but it's okay for debugging whilst development.
It's ($foo = 0) or 1;. or has a lower operator precedence than = .
You should use || in this case, since it has a higher precedence than =, and thus will evaluate as you'd expect.
IIRC, the assignment operator (=) has higher precedence than or. Thus, the last line would be interpreted as:
($foo = 0) or 1;
Which is a statement that assigns 0 to $foo, but returns 1. The fist statement is interpreted as:
echo(0 or 1);
An as such will print 1.
Order of operations. The word "or" has much lower precedence than the corresponding "||". Lower, even, than the assignment operator. So the assignment happens first, and the value of the assignment is the first operand to the "or".
"or" is meant more to be used for flow control than for logical operations. It lets you say something like
$x = get_something() or die("Couldn't do it!");
if get_something is coded to return false or 0 on failure.
In the first two snippets, you are comparing 0 or 1 (essentially true or false). In the third snippet you are assigning 0, which works, and thus is true, so therefore the or condition is not executed.emphasized text
In your third example, the = operator has a higher precedence than or, and thus gets done first. The || operator, superficially the same, has a higher precedence than =. As you say, interesting.

'AND' vs '&&' as operator

I have a codebase where developers decided to use AND and OR instead of && and ||.
I know that there is a difference in operators' precedence (&& goes before and), but with the given framework (PrestaShop to be precise) it is clearly not a reason.
Which version are you using? Is and more readable than &&? Or is there no difference?
If you use AND and OR, you'll eventually get tripped up by something like this:
$this_one = true;
$that = false;
$truthiness = $this_one and $that;
Want to guess what $truthiness equals?
If you said false... bzzzt, sorry, wrong!
$truthiness above has the value true. Why? = has a higher precedence than and. The addition of parentheses to show the implicit order makes this clearer:
($truthiness = $this_one) and $that
If you used && instead of and in the first code example, it would work as expected and be false.
As discussed in the comments below, this also works to get the correct value, as parentheses have higher precedence than =:
$truthiness = ($this_one and $that)
Depending on how it's being used, it might be necessary and even handy.
http://php.net/manual/en/language.operators.logical.php
// "||" has a greater precedence than "or"
// The result of the expression (false || true) is assigned to $e
// Acts like: ($e = (false || true))
$e = false || true;
// The constant false is assigned to $f and then true is ignored
// Acts like: (($f = false) or true)
$f = false or true;
But in most cases it seems like more of a developer taste thing, like every occurrence of this that I've seen in CodeIgniter framework like #Sarfraz has mentioned.
Since and has lower precedence than = you can use it in condition assignment:
if ($var = true && false) // Compare true with false and assign to $var
if ($var = true and false) // Assign true to $var and compare $var to false
For safety, I always parenthesise my comparisons and space them out. That way, I don't have to rely on operator precedence:
if(
((i==0) && (b==2))
||
((c==3) && !(f==5))
)
Precedence differs between && and and (&& has higher precedence than and), something that causes confusion when combined with a ternary operator. For instance,
$predA && $predB ? "foo" : "bar"
will return a string whereas
$predA and $predB ? "foo" : "bar"
will return a boolean.
Let me explain the difference between β€œand” - β€œ&&” - "&".
"&&" and "and" both are logical AND operations and they do the same thing, but the operator precedence is different.
The precedence (priority) of an operator specifies how "tightly" it binds two expressions together. For example, in the expression 1 + 5 * 3, the answer is 16 and not 18 because the multiplication ("*") operator has a higher precedence than the addition ("+") operator.
Mixing them together in single operation, could give you unexpected results in some cases
I recommend always using &&, but that's your choice.
On the other hand "&" is a bitwise AND operation. It's used for the evaluation and manipulation of specific bits within the integer value.
Example if you do (14 & 7) the result would be 6.
7 = 0111
14 = 1110
------------
= 0110 == 6
which version are you using?
If the coding standards for the particular codebase I am writing code for specifies which operator should be used, I'll definitely use that. If not, and the code dictates which should be used (not often, can be easily worked around) then I'll use that. Otherwise, probably &&.
Is 'and' more readable than '&&'?
Is it more readable to you. The answer is yes and no depending on many factors including the code around the operator and indeed the person reading it!
|| there is ~ difference?
Yes. See logical operators for || and bitwise operators for ~.
Another nice example using if statements without = assignment operations.
if (true || true && false); // is the same as:
if (true || (true && false)); // TRUE
and
if (true || true AND false); // is the same as:
if ((true || true) && false); // FALSE
because AND has a lower precedence and thus || a higher precedence.
These are different in the cases of true, false, false and true, true, false.
See https://ideone.com/lsqovs for en elaborate example.
I guess it's a matter of taste, although (mistakenly) mixing them up might cause some undesired behaviors:
true && false || false; // returns false
true and false || false; // returns true
Hence, using && and || is safer for they have the highest precedence. In what regards to readability, I'd say these operators are universal enough.
UPDATE: About the comments saying that both operations return false ... well, in fact the code above does not return anything, I'm sorry for the ambiguity. To clarify: the behavior in the second case depends on how the result of the operation is used. Observe how the precedence of operators comes into play here:
var_dump(true and false || false); // bool(false)
$a = true and false || false; var_dump($a); // bool(true)
The reason why $a === true is because the assignment operator has precedence over any logical operator, as already very well explained in other answers.
Here's a little counter example:
$a = true;
$b = true;
$c = $a & $b;
var_dump(true === $c);
output:
bool(false)
I'd say this kind of typo is far more likely to cause insidious problems (in much the same way as = vs ==) and is far less likely to be noticed than adn/ro typos which will flag as syntax errors. I also find and/or is much easier to read. FWIW, most PHP frameworks that express a preference (most don't) specify and/or. I've also never run into a real, non-contrived case where it would have mattered.

Categories