PHP7: Spaceship operator

PHP7: Spaceship Operator

Continua la nostra serie di tutorial alla scoperta delle novità di PHP 7 con un nuovo operatore già presente in altri linguaggi: il Combined Comparison Operator, o, per gli amici, Spaceship operator.

Lo spaceship operator prende il nome dal suo aspetto visivo, simile ad una navetta spaziale di Star Wars: <=>. Come lascia intendere il suo nome più tecnico, si tratta di un operatore di confronto che combina operatori già conosciuti e ne condivide le regole: <, <=, ==, >=, >.

A cosa serve lo spaceship operator?

Lo spaceship operator effettua un confronto tra due espressioni e ritorna tre possibili valori:

  • -1 se l’espressione a destra dell’operatore è maggiore
  • 0 se le due espressioni sono uguali
  • 1 se l’espressione a sinistra dell’operatore è maggiore

Questo operatore ha un comportamento molto simile alle funzioni strcmp() e version_compare(), ma può operare con tutti i tipi di valori generici del PHP. Ci permette quindi di confrontare interi, numeri in virgola mobile, stringhe, array e oggetti. La maggiore utilità si ha utilizzando l’operatore di confronto combinato in una callback della funzione usort().

Esempi

Lo spaceship operator è più semplice da capire che da spiegare, quindi procediamo con qualche esempio.

Per prima cosa vediamo qual è il valore ritornato dall’operatore in diversi contesti:

// Interi
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1

// Float
echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1

// Stringhe - viene effettuato un confronto secondo l'ordine alfabetico.
echo 'a' <=> 'a'; // 0
echo 'a' <=> 'b'; // -1
echo 'b' <=> 'a'; // 1

echo 'a' <=> 'aa'; // -1
echo 'zz' <=> 'aa'; // 1
echo 'z' <=> 'aa'; // 1

// Array
echo [] <=> []; // 0
echo [1, 2, 3] <=> [1, 2, 3]; // 0
echo [1, 2, 3] <=> []; // 1
echo [1, 2, 3] <=> [1, 2, 1]; // 1
echo [1, 2, 3] <=> [1, 2, 4]; // -1

// Oggetti
$a = (object) ['a' => 'b'];
$b = (object) ['a' => 'b'];
echo $a <=> $b; // 0

$a = (object) ['a' => 'b'];
$b = (object) ['a' => 'c'];
echo $a <=> $b; // -1
echo $b <=> $a; // 1

$a = (object) ['a' => 'b'];
$b = (object) ['b' => 'b'];
echo $a <=> $b; // 1

Vediamo quindi di applicare quanto abbiamo finora compreso, utilizzando proprio la funzione usort(). Per chi non lo ricordasse, usort() è una funzione distruttiva (lavora direttamente sull’array passato come primo parametro, non ne ritorna uno nuovo) che ordina un array secondo una callback definita dall’utente. Molti sviluppatori, sbagliando, utilizzano funzioni di confronto come la seguente:

function order_func($a, $b) {
    return $a >= $b;
}

I parametri passati alla callback sono due valori presenti nell’array passato come primo argomento ad usort(). Questo confronto è sbagliato poiché la callback usata come funzione di confronto deve ritornare un intero minore, uguale o maggiore a zero se il primo argomento è rispettivamente minore, uguale o maggiore del secondo. Esattamente quello che fa lo spaceship operator. Perché allora la callback order_func() dell’esempio precedente è errata?

$a = 1;
$b = 1;
echo (int) $a >= $b; // 1

$a = 1;
$b = 2;
echo (int) $a >= $b; // 0

$a = 2;
$b = 1;
echo (int) $a >= $b; // 1

Come vediamo la nostra order_func() ritorna 1 invece di 0 se le due espressioni sono uguali, mentre ritorna 0 se l’espressione a sinistra è minore dell’espressione a destra (mentre dovrebbe ritornare -1).

Un esempio corretto di una callback per usort() nelle versioni di PHP inferiori alla 7 è il seguente:

function order_func_good($a, $b) {
    return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);
}

Un po’ complicata, vero? Ecco come possiamo renderla con PHP7:

function order_func_php7($a, $b) {
    return $a <=> $b;
}

Vogliamo spingerci oltre?
Abbiamo cinque amici e vogliamo ordinarli per età, nome di battesimo e statura. Come possiamo fare?

$friends = [
    (object) ['name' => 'Marco', 'age' => 25, 'height' => 1.75],
    (object) ['name' => 'Elisa', 'age' => 32, 'height' => 1.54],
    (object) ['name' => 'Matteo', 'age' => 25, 'height' => 1.77],
    (object) ['name' => 'Elisa', 'age' => 32, 'height' => 1.62],
    (object) ['name' => 'Marco', 'age' => 23, 'height' => 1.75]
];

Possiamo usare due diverse callback per raggiungere lo stesso risultato:

function order_friends($a, $b) {
    return [$a->age, $a->name, $a->height] <=> [$b->age, $b->name, $b->height];
}

Oppure:

function order_friends($a, $b) {
    return ($a->age <=> $b->age) ?: ($a->name <=> $b->name) ?: ($a->height <=> $b->height);
}

Vediamo cosa succede ordinando i nostri amici in questo modo:

usort($friends, 'order_friends');
print_r($friends);
Array
(
 [0] => stdClass Object
 (
 [name] => Marco
 [age] => 23
 [height] => 1.75
 )

 [1] => stdClass Object
 (
 [name] => Marco
 [age] => 25
 [height] => 1.75
 )

 [2] => stdClass Object
 (
 [name] => Matteo
 [age] => 25
 [height] => 1.77
 )

 [3] => stdClass Object
 (
 [name] => Elisa
 [age] => 32
 [height] => 1.54
 )

 [4] => stdClass Object
 (
 [name] => Elisa
 [age] => 32
 [height] => 1.62
 )
)

La funzione order_friends() che abbiamo definito ordina gli oggetti prima per età, poi per nome e infine per altezza. Per questo troviamo al primo posto Marco, di 23 anni, seguito da Marco di 25. A parità di età troviamo Matteo, il cui nome viene alfabeticamente dopo quello di Marco. Infine troviamo le due Elisa, prima quella di statura inferiore e poi quella di statura maggiore. Questo indipendentemente da quale delle due funzioni order_friends() si usi, dal momento che si equivalgono.

Spero di averti fatto capire in modo semplice il funzionamento del nuovo spaceship operator introdotto in PHP7. Hai qualche riflessione in merito? Lascia un commento!

Non perderti i nostri tutorial!
Iscriverti per ricevere gratuitamente i nostri tutorial via e-mail.

Mattia Migliorini

Full Stack Web Developer e Consulente di Digital Marketing con formazione specifica in ambito e-commerce. Ama tenersi al passo con le tecnologie più recenti per offrire soluzioni sempre più semplici e potenti. È sempre pronto a mettere in discussione le proprie posizioni per ricercare nuove strategie creative.

Potrebbero interessarti anche...

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *