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, ...).