PHP: What does this symbol mean?
Searching for a special character is awkward. That is exactly why, in somebody else’s PHP code, you keep running into operators you cannot place. This article goes through the ones where that happens most often.
Every piece of output in this article comes from a script I ran against PHP 8.5.9.
== and === (type juggling)
== compares after an automatic type conversion, === compares the data type
as well:
'1' == '01' => true
'10' == '1e1' => true
100 == '1e2' => true
'1' === '01' => false
So two numeric strings are compared as numbers. Careful, one classic changed with PHP 8:
0 == 'a' => false
Before PHP 8 this was true. Back then the string was cast to int, which gave
0. Since PHP 8 the number is cast to a string instead. If an older tutorial
says otherwise, that is why.
When in doubt, use ===.
?: and ?? are not the same thing
This is the one most people get caught by, and it is even stated incorrectly on
Stack Overflow. The short form of the ternary (?:, often called the “Elvis
operator”) tests for truthiness. The null coalescing operator (??, since
PHP 7) tests for null:
<?php
$zero = 0;
echo $zero ?: 'fallback'; // 'fallback' -- 0 is falsy
echo $zero ?? 'fallback'; // 0 -- 0 is not null
?>
So for 0, '' or [] the two behave differently. On top of that, ?? also
suppresses the warning for a missing array key, whereas ?: does not.
<?php
$arr = [];
echo $arr['missing'] ?? 'default'; // 'default', no warning
?>
?? can also be chained. The first value that exists and is not null wins:
<?php
$name = $_GET['user'] ?? $_POST['user'] ?? 'nobody';
?>
Since PHP 7.4 there is a matching assignment, ??=. It only sets a value if it
is not set already:
<?php
$config = ['host' => 'localhost'];
$config['port'] ??= 3306; // gets set
$config['host'] ??= 'ignored'; // stays 'localhost'
?>
?-> the nullsafe operator
Since PHP 8.0. The operator aborts the chain and yields null instead of raising
an error:
$withName?->name?->first => 'Ada'
$withoutName?->name?->first => NULL
That saves you a stack of nested checks. One detail: it does not work on array keys, where you still get a warning.
<=> the spaceship operator
Since PHP 7.0. It compares in one go and returns 0, a negative or a positive
value:
1 <=> 2 => -1
2 <=> 1 => 1
"a" <=> "a" => 0
[1,2,3] <=> [1,2,4] => -1
The main use case is comparison callbacks, for instance with usort():
<?php
$numbers = [4, 2, 1, 3];
usort($numbers, fn($a, $b) => $a <=> $b); // [1, 2, 3, 4]
?>
A common mistake is to write $a - $b instead. That breaks as soon as floats are
involved, because usort() casts the return value to int. The difference
between 4 and 4.6 therefore becomes 0, meaning “equal”.
… the three dots
Depending on where they appear, the three dots do three different things.
Variadic parameters (since PHP 5.6), that is, any number of arguments:
<?php
function total(int ...$numbers): int {
return array_sum($numbers);
}
echo total(1, 2, 3); // 6
?>
Argument unpacking, that is, taking an array apart:
total(...[10, 20, 30]) => 60
With associative arrays this works like named arguments, so the order no longer matters:
fullName(...['last' => 'Lovelace', 'first' => 'Ada']) => 'Ada Lovelace'
First class callable syntax (since PHP 8.1). This turns a function into a value:
<?php
$upper = strtoupper(...);
echo $upper('abc'); // ABC
?>
=> and -> and ::
These three get mixed up a lot.
=> separates key and value in an array:
<?php
$arr = ['year' => 2026];
echo $arr['year']; // 2026
?>
-> accesses something on an object, :: something on the class itself
(constants, static methods and properties):
<?php
echo (new Counter)->value; // instance
echo Counter::START; // constant
echo Counter::make()->value; // static method
?>
. and + on arrays
The dot concatenates strings. On arrays, though, + is not the same as
array_merge():
[1,2] + [9,9,9] => [1, 2, 9]
array_merge([1,2], [9,9,9]) => [1, 2, 9, 9, 9]
+ is the union: a key that already exists on the left is not taken from the
right. array_merge() renumbers numeric keys and therefore loses nothing.
@ the error control operator
The @ suppresses the error messages of an expression:
@file_get_contents('/does/not/exist') => false
The warning is gone, but the call failed all the same. That is precisely the
problem: you can no longer see that something went wrong, yet you still have to
check the return value. So avoid @ wherever you can.
«< heredoc and nowdoc
Both allow multi-line strings. The difference is in the quotes around the identifier:
heredoc (<<<TXT) => 'hello world'
nowdoc (<<<'TXT') => 'hello $who'
Heredoc behaves like a double-quoted string and interpolates variables. Nowdoc behaves like single quotes and leaves everything as it is.
|> the pipe operator
New in PHP 8.5. It passes the result along to the right, so that nested calls can be read from left to right:
<?php
$title = ' PHP 8.5 Released ';
$slug = $title
|> trim(...)
|> (fn($s) => str_replace(' ', '-', $s))
|> (fn($s) => str_replace('.', '', $s))
|> strtolower(...);
// 'php-85-released'
?>
Before PHP 8.5 you had to write the same thing inside out:
<?php
$slug = strtolower(str_replace('.', '', str_replace(' ', '-', trim($title))));
?>
** and %
** is exponentiation (since PHP 5.6), % the remainder of a division:
2 ** 10 => 1024
7 % 3 => 1
-7 % 3 => -1
With modulo the sign of the result follows the left operand. If you need the remainder to always be positive, there is no way around handling that yourself.
And the rest?
The complete list of all tokens is in the
list of parser tokens in the PHP
manual. We have deliberately left out the bitwise operators (&, |, ^, ~,
<<, >>) and variable variables ($$) here — perhaps more on those later.
About Netcup (advertisement)
The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).