All the operators
Arithmetic, comparison, logical, string, bitwise, null-safe, spaceship and pipe β what each one really does, and the precedence that decides who wins.
Arithmetic
<?php echo 7 + 2, ' ', 7 - 2, ' ', 7 * 2, "\n"; echo 7 / 2, "\n"; // 3.5 β always a float unless it divides exactly echo intdiv(7, 2), "\n"; // 3 β integer division echo 7 % 2, "\n"; // 1 β int modulo, sign follows the LEFT operand echo -7 % 2, "\n"; // -1 echo fmod(7.5, 2), "\n"; // 1.5 β float modulo echo 2 ** 10, "\n"; // 1024 β right-associative
Assignment
<?php $a = 10; $a += 5; $a -= 3; $a *= 2; $a /= 4; $a **= 2; echo $a, "\n"; $s = 'Hello'; $s .= ', world'; echo $s, "\n"; $x = null; $x ??= 'default'; // assign only if null or unset $x ??= 'ignored'; echo $x, "\n";
Comparison
| Operator | True when |
|---|---|
== | equal after type juggling |
=== | equal and same type |
!=, <> | not equal after juggling |
!== | not identical |
<, >, <=, >= | ordering |
<=> | -1, 0 or 1 β the spaceship |
<?php
var_dump(1 == '1', 1 === '1');
var_dump(0 == 'abc'); // false since PHP 8
var_dump('abc' <=> 'abd'); // -1
var_dump([1, 2] == [1, 2]); // true β same pairs
var_dump([1, 2] === [2 => 1]); // false β order and keys must matchβ οΈ == on arrays compares key/value pairs in any order; === also requires the same order and the same key types. Two arrays that print identically can still be !==.
Logical
&&/|| and and/or do the same thing at very different precedence β low enough that and binds looser than assignment.
<?php $a = true && false; $b = true and false; // parses as ($b = true) and false var_dump($a, $b); // false, TRUE β the classic trap
Both forms short-circuit: the right side is not evaluated if the left already decides the answer.
<?php
function loud(bool $v): bool {
echo " evaluated\n";
return $v;
}
echo "false && β¦\n";
var_dump(false && loud(true)); // right side never runs
echo "true || β¦\n";
var_dump(true || loud(true));π‘ Tip: Use && and ||. The only common use for or is the idiom $fh = fopen(β¦) or die(β¦), where the loose precedence is the point β and exceptions are a better answer than die().
Null handling
<?php
$config = ['name' => 'Ada'];
echo $config['name'] ?? 'anonymous', "\n"; // 'Ada'
echo $config['email'] ?? 'none', "\n"; // 'none' β no warning
echo $config['a']['b']['c'] ?? 'deep', "\n"; // safe all the way down
class User {
public function __construct(public ?User $manager = null) {}
public function name(): string { return 'Ada'; }
}
$u = new User();
echo $u->manager?->name() ?? 'no manager', "\n"; // ?-> stops at null<?php $qty = '0'; echo 'with ?: ', $qty ?: 1, "\n"; // 1 β probably wrong echo 'with ??: ', $qty ?? 1, "\n"; // 0 β probably right
Bitwise
<?php
$a = 0b1100; // 12
$b = 0b1010; // 10
printf("AND %04b\n", $a & $b);
printf("OR %04b\n", $a | $b);
printf("XOR %04b\n", $a ^ $b);
printf("<<1 %05b\n", $a << 1);
printf(">>1 %04b\n", $a >> 1);
const READ = 1, WRITE = 2, EXEC = 4;
$perm = READ | WRITE;
var_dump(($perm & WRITE) !== 0, ($perm & EXEC) !== 0);Type and existence
<?php
$obj = new stdClass();
$arr = [1, 2];
var_dump($obj instanceof stdClass);
var_dump(isset($arr[0]), isset($arr[9]));
var_dump(empty($arr), empty([]));
var_dump(array_key_exists(0, $arr));
$maybeNull = ['k' => null];
var_dump(isset($maybeNull['k'])); // false β isset means "set AND not null"
var_dump(array_key_exists('k', $maybeNull)); // trueThe pipe operator
PHP 8.5 adds |>, which feeds the left value into the callable on the right. It turns inside-out nesting into left-to-right reading.
<?php
$result = ' Hello World '
|> trim(...)
|> strtolower(...)
|> str_split(...)
|> array_unique(...)
|> count(...);
echo $result, "\n";
echo strlen(strtoupper(trim(' abc '))), "\n"; // nested
echo ' abc ' |> trim(...) |> strtoupper(...) |> strlen(...), "\n"; // pipedPrecedence, shortened
Highest to lowest. When in doubt, parenthesise β the reader should not have to consult this table.
** (right) ! ++ -- (casts) @ instanceof * / % + - << >> . (concatenation) < <= > >= == != === !== <=> & ^ | && || ?? ?: ?-> |> = += -= β¦ (right) and xor or
<?php echo 2 + 3 * 4, "\n"; // 14 β * binds tighter echo (2 + 3) * 4, "\n"; // 20 echo 2 ** 3 ** 2, "\n"; // 512 β right-associative: 2 ** 9 echo 'sum: ' . 1 + 2, "\n"; // since PHP 8: '.' binds LOOSER than '+' β "sum: 3" var_dump(true ? 'a' : (false ? 'b' : 'c'));
β οΈ Before PHP 8, 'sum: ' . 1 + 2 parsed as ('sum: ' . 1) + 2 and produced 2 with a warning. PHP 8 lowered . below +/- so it now means 'sum: ' . (1 + 2). Old code that relied on the old grouping changes behaviour silently.
Reference: Operators in the PHP Manual.