The Behaviour of the or Operator in PHP

The behaviour of the or operator in PHP

The basics:

  1. An assignment expression results in the assigned value.

    What does that mean? $foo = 'bar' is an expression, in which the assignment operator = assigns a value. An expression always returns a value itself. Just like the expression 1 + 2 results in the value 3, the expression $foo = 'bar' results in the value 'bar'. That's why this works:

    $foo = $bar = 'baz'; // which is: $foo = ($bar = 'baz');
  2. Boolean operations are short-circuiting operations. Both sides are not always evaluated if they don't need to be. true || false is always true overall, since the lefthand operand is true, so the whole expression must be true. false is not even being evaluated here.

  3. Operator precedence dictates in which order parts of an expression are grouped into sub-expressions. Higher precedence operators are grouped with their operands before lower precedence operators.

Therefore:

$e = false || true;

false || true is being evaluated, which results in the value true, which is assigned to $e. The || operator has a higher precedence than =, therefore false || true is grouped into an expression (as opposed to ($e = false) || true).

$f = false or true;

Here now or has a lower precedence than =, which means the assignment operation is grouped into one expression before or. So first the $f = false expression is evaluated, the result of which is false (see above). So then you have the simple expression false or true which is evaluated next and results in true, but which nobody cares about.

The evaluation works like this:

1. $f = false or true;
2. ($f = false) or true; // precedence grouping
3. false or true; // evaluation of left side ($f is now false)
4. true; // result

Now:

$foo or $foo = 5; 

Here, again, $foo = 5 has a higher precedence and is treated as one expression. Since it occurs on the right side of the or operator, the expression is only evaluated if necessary. It depends on what $foo is initially. If $foo is true, the right hand side will not be evaluated at all, since true or ($foo = 5) must be true overall. If $foo has a falsey value initially though, the right hand side is evaluated and 5 is assigned to $foo, which results in 5, which is true-ish, which means the overall expression is true, which nobody cares about.

1. $foo or $foo = 5;
2. $foo or ($foo = 5); // precedence grouping
3. false or ($foo = 5); // evaluation of left side
4. false or 5; // evaluation of right side ($foo is now 5)
5. true; // result

Strange behaviour on logical AND-Operators

It actually comes because && is different from AND.

$var1 = true;
$var2 = 0;

$var3 = $var1 && $var2;
var_export($var3); // Output: false

$var3 = $var1 AND $var2;
var_export($var3); // Output: true

In the && case you actually write $var3 = ( $var1 && $var2 )

In the AND case you actually write ( $var3 = $var1 ) AND $var2

That's because = has greater precedence then AND, but && has greater precedence then =

PHP Ternary Operators don't work as expected?

The ternary operator is left associative unlike most other languages such as C#. The code:

$active = is_null($param)
? true
: is_string($param)
? (strtolower($param) === 'true')
: true;

is evaluated as follows:

$active = ((is_null($param) ? true : is_string($param))
? (strtolower($param) === 'true') : true);

You must explicitly add parenthesis to make sure ?: works the way it does in familiar languages:

$active = is_null($param)
? true
: (is_string($param)
? (strtolower($param) === 'true')
: true);

Strange behaviour of ++ operator in PHP 5.3

In PHP you can increment strings (but you cannot "increase" strings using the addition operator, since the addition operator will cause a string to be cast to an int, you can only use the increment operator to "increase" strings!... see the last example):

So "a" + 1 is "b" after "z" comes "aa" and so on.

So after "Test" comes "Tesu"

You have to watch out for the above when making use of PHP's automatic type coercion.

Automatic type coercion:

<?php
$a="+10.5";
echo ++$a;

// Output: 11.5
// Automatic type coercion worked "intuitively"
?>


No automatic type coercion! (incrementing a string):

<?php
$a="$10.5";
echo ++$a;

// Output: $10.6
// $a was dealt with as a string!
?>



You have to do some extra work if you want to deal with the ASCII ordinals of letters.

If you want to convert letters to their ASCII ordinals use ord(), but this will only work on one letter at a time.

<?php
$a="Test";
foreach(str_split($a) as $value)
{
$a += ord($value); // string + number = number
// strings can only handle the increment operator
// not the addition operator (the addition operator
// will cast the string to an int).
}
echo ++$a;
?>

live example

The above makes use of the fact that strings can only be incremented in PHP. They cannot be increased using the addition operator. Using an addition operator on a string will cause it to be cast to an int, so:

Strings cannot be "increased" using the addition operator:

<?php
$a = 'Test';
$a = $a + 1;
echo $a;

// Output: 1
// Strings cannot be "added to", they can only be incremented using ++
// The above performs $a = ( (int) $a ) + 1;
?>

The above will try to cast "Test" to an (int) before adding 1. Casting "Test" to an (int) results in 0.


Note: You cannot decrement strings:

Note that character variables can be incremented but not decremented and even so only plain ASCII characters (a-z and A-Z) are supported.

The previous means that echo --$a; will actually print Test without changing the string at all.


Unexpected behaviour of !print(1) || 1 in php

The key thing here is to realise that print is not a function, and doesn't take arguments in parentheses - the parentheses aren't optional, they're just not part of the syntax at all.

When you write print("1"); the print statement has a single argument, the expression ("1"). That is if course just another way of writing "1" - you could add any number of parentheses and it wouldn't change the value.

So when you write print("1") || 1 the argument to print is the expression ("1") || 1. That expression is evaluated using PHP's type juggling rules as true || true which is true. Then it's passed to print and - completely coincidentally to what you were trying to print - is type juggled to the string "1".

The print statement is then treated as an expression returning true, and the ! makes it false, so the if statement doesn't run.

This is a good reason not to use parentheses next to keywords like print, require, and include - they give the mistaken impression of "attaching" an argument to the keyword.

Can't understand function and reference behaviour

It does not call $counter=0 for the second time. You call it just once when initiating the first function. When you call $add(), you call every time the second function (that is in your return statement) which just uses the modified value of $counter that you passed by reference. If you would add echo $counter; after the $counter = 0; you will see that.

PHP Bizarre behaviour with switch() and logical or (XOR) in a case declaration

case values have to be SINGLE values. You can't do 2 || 3, because that'll evaluate as

case (2 or 3) -> case TRUE

Similarly, if you were using && (and), you'd get

case (0 and 1) -> case FALSE

You can use 'fallthrough' behavior for the OR logic:

case 2:
case 3:
...code here ...
break;

XOR, you can't do. not with a simple case statement.

comment followup:

for ($i = 0; $i < 5; $i++) {
switch($i) {
case 0 xor 1: echo "0 xor 1: $i\n"; break;
case 2 xor 3: echo "2 xor 3: $i\n"; break;
}
}

will output

2x3: 0
0x1: 1
0x1: 2
0x1: 3
0x1: 4

and

php > var_dump(2 xor 3);
bool(false)
php > var_dump(0 xor 1);
bool(true)

Remember that switch cases evaluate the same ==. They're lazy comparisons, not checking types. So your 2 xor 3 is evaluated the same as if the case has been case false, which makes the $i=0 condition match.

Is this a bug in PHP null coalesce operator or expected behaviour?

This is expected behavior due to operator precedence

|| has higher precedence than ??, so your original statement is treated as

if ($foo['bar'] ?? (false || $foo['baz']) ?? false)


Related Topics



Leave a reply



Submit