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:
Associativity | Operators | Additional Information |
---|---|---|
(n/a) | clone new | clone and new |
right | ** | arithmetic |
(n/a) | + - ++ -- ~ (int) (float) (string) (array) (object) (bool) @ | arithmetic (unary + and - ), increment/decrement, bitwise, type casting and error control |
left | instanceof | type |
(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 from | yield from |
(n/a) | yield | yield |
(n/a) | print | |
left | and | logical |
left | xor | logical |
left | or | logical |
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