Riepilogo post nella categoria PHP 7

Una notevole miglioria di PHP7 è senza ombra di dubbio l'inserimento di un operatore di confronto combinato che viene chiamato Spaceship per semplicità comunicativa: infatti è un operatore di confronto che assomiglia tantissimo ad una navetta spaziale stilizzata, un pò come se fosse una emoticon.

Questo operatore è semplicemente definito come <=>

E' un operatore che va utilizzato per la comparazione di scalari, quindi, di numeri interi, bool, float, e stringhe, ma anche per la comparazione di Array e Oggetti. Ecco un esempio:

<?php
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1

La particolarità di questo operatore è che al posto di definire un "vero o falso", come accade per i suoi fratelli <, >, <= e >=, la sua mappa di verità è leggermente diversa, e permette di avere 3 stati di ritorno:

  • 0:  se entrambe le espressioni sono UGUALI
  • 1: se l'espressione di SINISTRA è maggiore
  • -1: se l'espressione di DESTRA è maggiore

Ora, ragionando un secondo su espressioni scalari "stringa", questo operatore fa quello che ha sempre fatto la funzione strcmp di php.

<?php
// qui è chiaramente visibile il comportamento dell'operatore Spaceship, che è assolutamente identico alla funzione strcmp per le stringhe
$a = 'aeroporto';
$b = 'piscina';
echo strcmp ( $a, $b ); // -1
echo $a <=> $b; // -1

La vera potenza di questo operatore è che rende la scrittura di funzioni di ordinamento custom molto più veloce da scrivere: ecco un esempio di stessa identica funzione di ordinamento custom, usando il "vecchio" triplo if, e usando il nuovo operatore Spaceship:

<?php
$numeri = [ 0 => [ 'v' => 10 ], 1 => [ 'v' => 20 ], 2 => [ 'v' => 30 ], 3 => [ 'v' => 10 ], 4 => [ 'v' => 40 ], 5 => [ 'v' => 40 ] ];

// vecchio metodo di ordinamento
usort ( $numeri, function ( $n1, $n2 ) {
   if ( $n1['v'] == $n2['v'] ) return 0;
   else if ( $n1['v'] < $n2['v'] ) return 1;
   else return -1;
});

// nuovo metodo di ordinamento
usort ( $numeri, function ( $n1, $n2 ) {
   return $n1['v'] <=> $n2['v'];
});

Vediamo invece qualche esempio di confronto tra più di un tipo di variabile e i relativi ritorni di verità:

<?php
// 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
echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1
echo "a" <=> "aa"; // -1
echo "zz" <=> "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
$a = (object) ["a" => "c"];
$b = (object) ["a" => "b"];
echo $a <=> $b; // 1
// solo i valori vengono confrontati, non le chiavi: in questo caso i valori sono "b" e "b" in entrambi gli oggetti
$a = (object) ["a" => "b"];
$b = (object) ["b" => "b"];
echo $a <=> $b; // 0

La migliore applicazione di questo comparatore è l'utilizzo in funzioni di ordinamento custom:

<?php
class Veicolo {
   public $nome;
   public $velocitaMassima;
   public function __construct ( string $nome, int $velocitaMassima ) {
      $this -> nome = $nome;
      $this -> velocitaMassima = $velocitaMassima;
   }
}

$veicoli = [
   new Veicolo ( 'Panda', 160 );
   new Veicolo ( 'Punto', 180 );
   new Veicolo ( 'Giulia', 240 );
   new Veicolo ( 'Ape', '50' );
   new Veicolo ( 'Ducato', 160 );
   new Veicolo ( 'Z4', 240 );
];

// ordinamento per nome
usort ( $veicoli, function ( $v1, $v2 ) {
   return $v1 -> nome <=> $v2 -> nome;
});
echo $veicoli[0] -> name;  // Ape

// ordinamento per velocità massima
usort ( $veicoli, function ( $v1, $v2 ) {
   return $v1 -> velocitaMassima <=> $v2 -> velocitaMassima;
});
echo $veicoli[0] -> name;  // Giulia

Vediamo come si comporta l'operatore Spaceship di PHP 7 quando utilizzato su ordinamenti di valori multipli. Questa tecnica semplifica enormemente l'ordinamento di array o oggetti basando l'ordinamento su più chiavi.

<?php
usort ( $veicoli, function ( $v1, $v2 ) {
   return [ $v1 -> velocitaMassima, $v1 -> nome ] <=> [ $v2 -> velocitaMassima, $v2 -> nome ];
});
foreach ( $veicoli as $veicolo ) {
   echo 'La velocità massima di "' $veicolo -> nome . '" è ' . $veicolo -> velocitaMassima . '\n';
}
// Stampa:
// La velocità massima di "Ape" è 50
// La velocità massima di "Ducato" è 160
// La velocità massima di "Panda" è 160
// La velocità massima di "Punto" è 180
// La velocità massima di "Giulia" è 240
// La velocità massima di "Z4" è 240

Sfruttando la comparazione di array, questa funzione di ordinamento ha richiesto 1 sola riga per essere scritta, al posto delle 6 o 7 richieste precedentemente.

Questa era la seconda lezione su PHP 7. Naviga nella categoria PHP7 di questo blog per scoprire le altre lezioni e le novità introdotte da PHP7.

Ragionando in PHP 7 Lezione 1: Tipizzazione scalare

Postato in PHP 7 | Programmazione PHP

PHP 5 ha introdotto a suo tempo la possibilità per lo sviluppatore di definire verbosamente i tipi di dato richiesti da una funzione. Questo impedisce l'esecuzione di funzioni o metodi richiamati con tipi di dati errati, come ad esempio passare un timestamp UNIX a un metodo che si aspetta un oggetto DateTime. In più, la tipizzazione rende anche chiaro agli altri sviluppatori come utilizzare la funzione in piena compatibilità. Per esempio, qui sotto, si analizzi la definizione delle due funzioni con e senza tipizzazione:

// Senza tipizzazione
function prossimoGiornoSettimana($data) { /*...*/ }
// Con tipizzazione (PHP5)
function prossimoGiornoSettimana(DateTime $data) { /*...*/ }

La tipizzazione era inizialmente limitata alle sole classi e interfacce, ma fu poi ampliata per consentire anche i tipi Array e Callable. PHP 7, invece, estende ulteriormente questo limite consentendo la tipizzazione scalare come int, float, string e bool:

// Tipizzazione scalare (PHP7)
// è possibile definire il tipo richiesto per il parametro $data come INT. E' chiaramente comprensibile allo sviluppatore che questa funzione accetta un timestamp.
function prossimoGiornoSettimana(int $data) { /*...*/ }

Controllo della tipizzazione

Il controllo di tipizzazione per i tipi scalari in PHP 7 funziona in modalità "Coercive", ovvero, nella stessa identica maniera con la quale le vecchie versioni di PHP effettuano il controllo di tipizzazione sulle funzioni built-in. Per meglio spiegare questo concetto, basta pensare alla situazione della funzione floor, che è così definita:

// definizione della funzione float built-in di PHP
float floor ( float $numero )

Quando si passa un parametro alla funzione float, PHP effettuerà una conversione "Coercive" del valore passato alla funzione, se questa è convertibile. Esiste una tabella di conversione per capire se un tipo di variabile può essere automaticamente convertito da PHP in modalità "Coercive":

Tipo di datointfloatstringboolobject
intsisi*si**sino
floatsisisi*sino
stringsisisisisi***
intsisisisino

*: Solo valori che non siano NaN e compresi tra PHP_INT_MIN e PHP_INT_MAX
**: solo se è una stringa numerica
***: solo se l'oggetto ha un metodo magico __toString()

PHP 7 introduce un nuovo metodo per il controllo della tipizzazione, chiamato "Strict", che permette allo sviluppatore di definire, all'inizio dello script, una particolare "frase" che, se presente, imporrà al compilatore di effettuare il controllo di tipizzazione in senso stretto, quindi senza interpolazione dei parametri "Coercive" come sopra spiegato. Attenzione: utilizzare il controllo di tipizzazione Strict farà in modo che anche eventuali funzioni built-in di PHP richiamate all'interno del file saranno sottoposte allo stesso identico trattamento di validazione.

La modalità Strict essenzialmente richiede che tutte le variabili passate alle funzioni tipizzate siano in regola con la definizione della funzione/metodo. In caso contrario, verrà lanciato un TypeError. Ad esempio, in modalità Strict, non è possibile utilizzare stringhe numeriche quando la tipizzazione richiede un float o un int (come invece in modalità Coercive è ammesso e funzionante):

<?php
declare(strict_types=1);
function ciao(string $nome): string {
   echo 'Ciao, ' . $name;
}
function somma(float $a, float $b): float {
   return $a + $b;
}
ciao('Alberto'); // Stampa: "Ciao, Alberto"
ciao(3); // genera un Fatal error: Uncaught TypeError: Argument 1 passed to ciao() must be of the type string, integer given
somma(2, 1); // ritorna "3" anche se i tipi passati sono int
somma(3.2, true); // genera un Fatal error: Uncaught TypeError: Argument 2 passed to somma() must be of the type float, bool given

Poiché la direttiva è impostata per ogni file, e ad inizio di ogni file, è possibile e ammesso combinare le modalità Strict e Coercive nell'applicazione. Ad esempio, se alcune parti della propria applicazione ( magari installate tramite Composer ) non richiedono tipizzazione stretta, mentre invece alcuni specifiche classi della propria applicazione la richiedono, tutta l'applicazione continuerà a funzionare, e il controllo di tipizzazione stretto verrà applicato solo ed esclusivamente sui file che ne fanno uso.

Compatibilità con versioni di PHP precedenti

Non è più possibile, con PHP 7, creare classi denominate int, float, string o bool in quanto queste andrebbero in conflitto con le nuove tipizzazioni di tipo scalare.

Questa era la prima lezione su PHP 7. Naviga nella categoria PHP7 di questo blog per scoprire le altre lezioni e le novità introdotte da PHP7