Operator Precedence
What Is Operator Precedence? In simple terms: When an expression contains several operators, precedence decides which operator is applied first — just as multiplication is done before addition in math
What Is Operator Precedence?
In simple terms: When an expression contains several operators, precedence decides which operator is applied first — just as multiplication is done before addition in mathematics.
Technically: Every PHP operator has a precedence level. Operators with higher precedence bind more tightly to their operands. When operators share the same level, associativity decides the grouping: left to right, right to left, or not allowed at all (non-associative).
echo 2 + 3 * 4; // 14, because * binds tighter than +
PHP reads this as 2 + (3 * 4), not (2 + 3) * 4.
Why It Matters
Most precedence rules feel natural, but some do not — especially when concatenation, null coalescing, logical keywords, negation, or bitwise operators are mixed with other operators. Many subtle bugs come from an expression being grouped differently than the author intended. Understanding precedence lets you read existing code correctly and know when parentheses are required.
Precedence Table
From highest precedence (applied first) to lowest:
| Level | Operators | Associativity | Notes |
|---|---|---|---|
| 1 | clone, new | — | Object creation |
| 2 | ** | right | Exponentiation |
| 3 | + - (unary), ++, --, ~, (int), (float), (string), (array), (object), (bool), @ | — | Unary operators and casts |
| 4 | instanceof | left | Type check |
| 5 | ! | — | Logical NOT |
| 6 | * / % | left | Multiplicative |
| 7 | + - | left | Additive |
| 8 | << >> | left | Bit shift |
| 9 | . | left | Concatenation (lower than +/- since PHP 8.0) |
| 10 | < <= > >= | non-associative | Ordering comparison |
| 11 | == != === !== <> <=> | non-associative | Equality |
| 12 | & | left | Bitwise AND |
| 13 | ^ | left | Bitwise XOR |
| 14 | | | left | Bitwise OR |
| 15 | && | left | Logical AND |
| 16 | || | left | Logical OR |
| 17 | ?? | right | Null coalescing |
| 18 | ? : | non-associative | Ternary |
| 19 | = += -= *= /= .= %= **= ??= &= |= ^= <<= >>= | right | Assignment |
| 20 | yield from, yield, print | — | |
| 21 | and | left | Logical AND (keyword) |
| 22 | xor | left | Logical XOR (keyword) |
| 23 | or | left | Logical OR (keyword) |
You do not need to memorize this table. The practical skill is knowing where the surprises are — covered in the rest of this lesson.
Associativity
When an expression uses several operators of the same precedence, associativity decides the grouping.
Example
<?php
echo 10 - 4 - 3 . "\n"; // left: (10 - 4) - 3
echo 100 / 10 / 2 . "\n"; // left: (100 / 10) / 2
echo 2 ** 3 ** 2 . "\n"; // right: 2 ** (3 ** 2)
$a = $b = 5; // right: $a = ($b = 5)
echo "$a $b\n";
Output
3
5
512
5 5
- Left-associative operators (
-,/,.,&&, …) group from left to right, which matches normal reading order. - Right-associative operators (
**,??,=) group from right to left. That is why$a = $b = 5assigns5to both variables and$a ?? $b ?? $cchecks$afirst, then$b, then$c.
Non-Associative Operators
Comparison operators cannot be chained:
<?php
$x = 5;
var_dump(1 < $x < 10);
Result
PHP Parse error: syntax error, unexpected token "<"
PHP refuses to guess what 1 < $x < 10 means. Write the range check explicitly: 1 < $x && $x < 10.
Arithmetic
<?php
var_dump(2 + 3 * 4);
var_dump((2 + 3) * 4);
var_dump(-3 ** 2);
var_dump((-3) ** 2);
Output
int(14)
int(20)
int(-9)
int(9)
** has higher precedence than unary minus, so -3 ** 2 is -(3 ** 2). Use parentheses when the base is negative.
Concatenation and Arithmetic
<?php
echo "Sum: " . 4 + 5 . "\n";
echo "Product: " . 4 * 5 . "\n";
echo "Sum: " . (4 + 5) . "\n";
Output (PHP 8.0 and newer)
Sum: 9
Product: 20
Sum: 9
Since PHP 8.0, + and - have higher precedence than ., so the addition happens first. In PHP 7, ., +, and - had the same precedence and were evaluated left to right, which produced "Sum: 4" + 5 — a warning and a wrong result. Code that must behave the same in both versions, and code that should be easy to read, uses parentheses as in the third line.
Casts
Casts are unary operators with high precedence. They apply only to the value directly after them:
<?php
var_dump((int) "3.7" + 0.5);
var_dump((int) ("3.7" + 0.5));
Output
float(3.5)
int(4)
- First line:
(int) "3.7"is3, then3 + 0.5is3.5. - Second line: the parentheses make PHP add first (
4.2) and then cast to4.
! and instanceof
instanceof has higher precedence than !:
<?php
$date = new DateTime();
var_dump(!$date instanceof DateTime);
var_dump(!($date instanceof DateTime));
Output
bool(false)
bool(false)
Both lines mean "not an instance of DateTime", because instanceof is applied before !. The first form works, but the parenthesized version is clearer to readers who do not remember this rule.
By contrast, ! binds tighter than comparison operators, so !$count > 0 means (!$count) > 0, not !($count > 0) — see Logical Operators.
Bitwise Operators and Comparisons
Bitwise &, ^, and | have lower precedence than == and other comparisons:
<?php
$flags = 5; // binary 0101
$mask = 3; // binary 0011
var_dump($flags & $mask == 3);
var_dump(($flags & $mask) == 3);
Output
int(1)
bool(false)
- First line: PHP evaluates
$mask == 3first (true), then5 & true, which is5 & 1=1. The result is an integer, not the intended comparison. - Second line: the parentheses perform the bitwise AND first (
0101 & 0011 = 0001=1) and then compare1 == 3.
Always parenthesize bitwise operations inside comparisons.
Logical Operators and Assignment
| Operators | Precedence Relative to = |
|---|---|
&&, ||, ??, ?: | Higher — evaluated before the assignment |
and, xor, or | Lower — the assignment happens first |
<?php
$x = true && false;
$y = true and false;
var_dump($x, $y);
Output
bool(false)
bool(true)
$y = true and false is grouped as ($y = true) and false. This is the main reason to prefer && and || over and and or.
&& Before ||
<?php
var_dump(true || false && false);
var_dump((true || false) && false);
Output
bool(true)
bool(false)
&& binds tighter than ||, so the first line is true || (false && false).
Null Coalescing
?? has lower precedence than arithmetic, concatenation, and comparison, so it often applies to a larger part of the expression than intended:
<?php
$price = 100;
$qty = 3;
var_dump($price ?? 0 * $qty);
var_dump(($price ?? 0) * $qty);
Output
int(100)
int(300)
The first line means $price ?? (0 * $qty). Wrap ?? in parentheses whenever it appears inside a larger expression. More examples are in Null Coalescing Operator.
Nested Ternaries Require Parentheses
The ternary operator is non-associative since PHP 8.0. Nesting it without parentheses is a compile-time error:
<?php
$score = 75;
echo $score >= 90 ? 'A' : $score >= 75 ? 'B' : 'C';
Result
PHP Fatal error: Unparenthesized `a ? b : c ? d : e` is not supported. Use either `(a ? b : c) ? d : e` or `a ? b : (c ? d : e)`
In PHP 7 and earlier, this expression was silently grouped from the left — the opposite of most other languages — and produced wrong results, which is why PHP 8 forbids it. Add parentheses:
<?php
$score = 75;
echo $score >= 90 ? 'A' : ($score >= 75 ? 'B' : 'C');
Output
B
For more than two branches, match (true) or if/elseif is usually clearer than nested ternaries (see PHP Match).
Object Creation and Method Calls (PHP 8.4)
Before PHP 8.4, calling a method directly on a newly created object required wrapping the new expression in parentheses. PHP 8.4 removed that requirement:
<?php
class Greeting
{
public function __construct(private string $name) {}
public function text(): string
{
return "Hello, {$this->name}!";
}
}
echo (new Greeting('Asha'))->text() . "\n"; // all PHP versions
echo new Greeting('Bala')->text() . "\n"; // PHP 8.4+
Output
Hello, Asha!
Hello, Bala!
The second form is a parse error on PHP 8.3 and older. Use the parenthesized form if your code must support older versions.
Precedence Is Not Evaluation Order
Precedence determines how an expression is grouped, not necessarily the order in which operands are evaluated. For example, in $a + $b * $c, precedence tells you that $b * $c is one group, but PHP does not guarantee whether $a is read before or after that group. This rarely matters — unless operands have side effects:
$i = 1;
echo $i + $i++; // result is not guaranteed by the language
The PHP manual explicitly warns that such code may behave differently between versions. Never read and modify the same variable in a single expression.
Practical Example: Making a Complex Condition Readable
A signup rule says: users may register if they are adults in India, or if they are at least 13 and a guardian has given consent.
Hard to Read
<?php
$age = 15;
$country = 'IN';
$hasGuardianConsent = true;
$canRegister = $age >= 18 && $country === 'IN' || $hasGuardianConsent && $age >= 13;
var_dump($canRegister);
Output
bool(true)
The result is correct, but only because && binds tighter than ||. A reader must know the precedence rules to verify it, and a small edit (for example, adding or) could silently change the meaning.
Clear Version
<?php
$age = 15;
$country = 'IN';
$hasGuardianConsent = true;
$isAdultInIndia = $age >= 18 && $country === 'IN';
$isTeenWithConsent = $age >= 13 && $hasGuardianConsent;
$canRegister = $isAdultInIndia || $isTeenWithConsent;
var_dump($canRegister);
Output
bool(true)
Why the Second Version Is Better
- Each sub-rule has a name that describes the business rule.
- Each line contains only one kind of logical operator, so no precedence knowledge is needed.
- Each part can be checked or tested separately.
When a single expression grows too long, splitting it into named variables is usually better than adding more parentheses.
Common Mistakes
| Mistake | What PHP Does | Fix |
|---|---|---|
-2 ** 2 | -(2 ** 2) = -4 | (-2) ** 2 |
$a & $mask == 3 | $a & ($mask == 3) | ($a & $mask) == 3 |
'Hi ' . $name ?? 'Guest' | ('Hi ' . $name) ?? 'Guest' | 'Hi ' . ($name ?? 'Guest') |
$ok = $a and $b | ($ok = $a) and $b | $ok = $a && $b |
!$x > 0 | (!$x) > 0 | !($x > 0) or $x <= 0 |
$a ? 'x' : $b ? 'y' : 'z' | Fatal error in PHP 8 | Add parentheses or use match |
1 < $x < 10 | Parse error | 1 < $x && $x < 10 |
Best Practices
- Use parentheses whenever an expression mixes different kinds of operators and the grouping is not obvious at a glance.
- Use
&&/||instead ofand/or. - Wrap
??, bitwise operations, and nested ternaries in parentheses. - Split long expressions into well-named variables.
- Never modify and read the same variable within one expression.
Official reference: https://www.php.net/manual/en/language.operators.precedence.php