Less known PHP operators

PHP offers a rich set of operators, some of which are lesser-known yet incredibly powerful for writing cleaner and more efficient code. In this article, we’ll explore a selection of these hidden gems, complete with examples and explanations to help you integrate them into your programming arsenal. Whether you’re dealing with array manipulation, custom comparisons, or optimizing assignments, these operators can make your code more elegant and easier to maintain.

Null Coalescing Assignment Operator

The null coalescing assignment operator is a useful but lesser-known feature introduced in PHP version 7.4. Here’s a concise example of how it works:

<?php
declare(strict_types=1);

class Payload {
    public function __construct()
    {
        print "constructor called\n";
    }
}

foreach(['test1', 'test2', 'test3'] as $test) {
    $payload ??= new Payload;
    print "$test\n";
}

Output:

constructor called
test1
test2
test3

In the first iteration of the loop, a Payload object is created. This approach is useful because it eliminates the need to explicitly check if the object exists before entering the loop. Essentially, the above example is equivalent to:

<?php
declare(strict_types=1);

class Payload {
    public function __construct()
    {
        print "constructor called\n";
    }
}

$cases = ["test1", "test2", "test3"];

$payload = null;
if (!empty($cases)) {
    $payload = new Payload;

}
foreach($cases as $test) {
    print "$test\n";
}

Using the null coalescing assignment operator simplifies the code significantly.

Spaceship operator

The spaceship operator (<=>) is incredibly useful for custom comparison functions. Let’s first look at an example without the spaceship operator:

<?php
declare(strict_types=1);

$numbers = [10, 5, 20, 15];

usort($numbers, function ($a, $b) {
    if ($a < $b) {
        return -1; // Return negative, $a comes before $b
    } elseif ($a > $b) {
        return 1; // Return positive, $a comes after $b
    } else {
        return 0; // They're equal
    }
});

// $numbers will now be sorted in ascending order.
print_r($numbers);

While functional, this approach can be verbose and error-prone. Using the spaceship operator simplifies it:

<?php
declare(strict_types=1);

$numbers = [10, 5, 20, 15];

usort($numbers, function ($a, $b) {
    return $a <=> $b; // -1, 0, 1 result is handled directly.
});

// $numbers will now be sorted in ascending order.
print_r($numbers);

To order in descending order just swap $a and $b. This is cleaner and easier to maintain.

Quick cast to int or float

Casting a string to an int or float can be done with the unary plus (+) operator:

<?php
declare(strict_types=1);

$test = "5.5";
var_dump(+$test);

This will cast the string "5.5" to a float. Note that if the cast is not possible, a TypeError will be thrown, which can be caught as needed.

Assignment operator

The assignment operator (=) has a feature often overlooked: it returns the assigned value. This can be leveraged to write efficient code. Consider the following example:

<?php
declare(strict_types=1);

class SomeClassWithSimpleCache{
    protected array $cache = [];

    public function get($key, Callable $callable) {
        if (isset($this->cache[$key])) {
            return $this->cache[$key];
        }
        return $this->cache[$key] = $callable($key);
    }
}

$cache = new SomeClassWithSimpleCache;
$f = function($id) { echo "fetching data from db for:", $id, "\n"; return 'test';};
$value = $cache->get('1', $f);
$value = $cache->get('1', $f);
$value = $cache->get('2', $f);
$value = $cache->get('2', $f);
$value = $cache->get('2', $f);
$value = $cache->get('2', $f);

Output:

fetching data from db for:1
fetching data from db for:2

To make it even simpler, you can combine it with the null coalescing assignment operator:

<?php
declare(strict_types=1);

class SomeClassWithSimpleCache{
    protected array $cache = [];

    public function get($key, Callable $callable) {
        return $this->cache[$key] ??= $callable($key);
    }
}

Array + Array operators

The + operator can be used to create the union of two or more arrays:

<?php
declare(strict_types=1);

$a = array("a" => "apple", "b" => "banana");
$b = array("a" => "pear", "b" => "strawberry", "c" => "cherry");

$c = $a + $b; // Union of $a and $b
echo "Union of \$a and \$b: \n";
var_dump($c);

Output:

Union of $a and $b:
array(3) {
  ["a"]=>
  string(5) "apple"
  ["b"]=>
  string(6) "banana"
  ["c"]=>
  string(6) "cherry"
}

The left operand’s values are retained in case of key collisions, meaning if a key exists in both arrays, the value from the left operand takes precedence. This behavior underscores the importance of operand order:

$d = $b + $a;
echo "Union of \$b and \$a: \n";
var_dump($d);

Gives us different result:

Union of $b and $a:
array(3) {
  ["a"]=>
  string(4) "pear"
  ["b"]=>
  string(10) "strawberry"
  ["c"]=>
  string(6) "cherry"
}

A shorthand version is also available: $a += $b is equivalent to $a = $a + $b.

Comparing 2 arrays

Arrays can be compared using == and ===. The double equals (==) checks if two arrays have the same keys and values, while the triple equals (===) ensures they are in the same order and of the same type:

<?php
declare(strict_types=1);

$a = array("apple", "banana");
$b = array(1 => "banana", "0" => "apple");

var_dump($a == $b); // bool(true)
var_dump($a === $b); // bool(false)

$c = array("apple", "banana", "3");
$d = array("apple", "banana", 3);

var_dump($c == $d); // bool(true)
var_dump($c === $d); // bool(false)

Operator precedence

Operator precedence can lead to unexpected results. Consider this example:

<?php
declare(strict_types=1);

$a = false or true;
$b = false || true;

var_dump($a);
var_dump($b);
var_dump($a === $b);

Can you tell me without reading further what will be the result? If not then check this table and try again to try to evaluate what will be the result:

AssociativityOperatorsAdditional Information
(n/a)clone newclone and new
right**arithmetic
(n/a)+ - ++ -- ~ (int) (float) (string) (array) (object) (bool) @arithmetic (unary + and -), increment/decrement, bitwise, type casting and error control
leftinstanceoftype
(n/a)!logical
left* / %arithmetic
left+ - .arithmetic (binary + and -), array and string (. prior to PHP 8.0.0)
left<< >>bitwise
left.string (as of PHP 8.0.0)
non-associative< <= > >=comparison
non-associative== != === !== <> <=>comparison
left&bitwise and references
left^bitwise
left|bitwise
left&&logical
left||logical
right??null coalescing
non-associative? :ternary (left-associative prior to PHP 8.0.0)
right= += -= *= **= /= .= %= &= |= ^= <<= >>= ??=assignment
(n/a)yield fromyield from
(n/a)yieldyield
(n/a)printprint
leftandlogical
leftxorlogical
leftorlogical
source

Output will look like this:

bool(false)
bool(true)
bool(false)

This behavior stems from the precedence of the = operator compared to or and ||. The || operator has a higher precedence than =, while or has a lower precedence. In the first line, the or operator is evaluated after the assignment, meaning $a is assigned the value false, and the or operation does not influence $a value. In contrast, the second line, $b is evaluated differently because the || operator takes precedence over =. Here, the false || true operation is performed first, resulting in true, which is then assigned to $b.

This distinction often surprises developers, and it’s a great example of why understanding operator precedence is critical.

Leave a Reply

Your email address will not be published. Required fields are marked *