Con la scusa di un po’ di esercizio vediamo anche quali sono le principali differenze tra (l’ormai vecchio) PHP 5 e il nuovo PHP 7 (che comunque è in circolazione già da un bel po’).
1. Sintassi flessibile per Heredoc e Newdoc
2. Operatore Spaceship
3. Null Coalesce Operator
4. Virgola finale negli argomenti di funzione
5. Migrazione a PCRE2
6. Unicode Codepoint Escape Syntax
7. Chiamata di errore nella codifica JSON
8. Exceptions per i fatal errors
9. Classi anonime
10. Dichiarazione del tipo di dato restituito
11. Metodo is_countable()
12. Primi e ultimi valori e chiavi di un array
13. Il metodo Closure->call
14. Prestazioni migliorate
1. Sintassi flessibile per Heredoc e Newdoc
Nell’ultimo aggiornamento del PHP 7.3 viene introdotta la possibilità di terminare il heredoc con UNA indentazione. Sottolineo UNA perché la cosa è un po’ comica, visto che una doppia indentazione (due tabulazioni) oppure degli spazi producono comunque errore.
In sostanza prima si doveva scrivere così:
|
$testo = <<<SQL SELECT t.* FROM tabella t SQL; echo $testo; |
Adesso è possibile scrivere così:
|
$testo = <<<SQL SELECT t.* FROM tabella t SQL; echo $testo; |
Questa singola tabulazione certo non farà la felicità assoluta di nessuno, però permette di aggiungere un po’ di chiarezza al codice (sebbene personalmente continui a trovarla una novità abbastanza inutile, tale qual’è).
2. Operatore Spaceship
A differenza della precedente, questa è una novità effettivamente significativa.
Viene introdotto l’operatore <=>
, chiamato spaceship, che fa le veci (quasi ma non del tutto) della funzione strcmp
. Questo velocizza la scrittura e ottimizza la sintassi, riducendo così il numero di passaggi a volte necessari.
Avendo il seguente codice:
|
$a = "a"; $b = "b"; echo "a <=> b : " . ($a <=> $b) . "<br>"; echo "mario <=> maria : " . ("mario" <=> "maria") . "<br>"; echo "marii <=> mario : " . ("marii" <=> "mario") . "<br>"; echo "maria <=> mario : " . ("maria" <=> "mario") . "<br>"; echo "mario <=> luigi : " . ("mario" <=> "luigi") . "<br>"; echo "1 <=> 2 : " . (1 <=> 2) . "<br>"; echo "2 <=> 1 : " . (2 <=> 1) . "<br>"; echo "3 <=> 3 : " . (3 <=> 3) . "<br>"; |
Avremo il seguente output:
a <=> b : -1
mario <=> maria : 1
marii <=> mario : -1
maria <=> mario : -1
mario <=> luigi : 1
1 <=> 2 : -1
2 <=> 1 : 1
3 <=> 3 : 0
Utilizzando invece strcmp
avremmo avuto il seguente codice:
|
echo "a <=> b : " . strcmp($a, $b) . "<br>"; echo "mario <=> maria : " . strcmp("mario", "maria") . "<br>"; echo "marii <=> mario : " . strcmp("marii", "mario") . "<br>"; echo "maria <=> mario : " . strcmp("maria", "mario") . "<br>"; echo "mario <=> luigi : " . strcmp("mario", "luigi") . "<br>"; echo "1 <=> 2 : " . strcmp(1, 2) . "<br>"; echo "2 <=> 1 : " . strcmp(2, 1) . "<br>"; echo "3 <=> 3 : " . strcmp(3, 3) . "<br>"; |
Ed il seguente output:
a <=> b : -1
mario <=> maria : 14
marii <=> mario : -6
maria <=> mario : -14
mario <=> luigi : 1
1 <=> 2 : -1
2 <=> 1 : 1
3 <=> 3 : 0
Come si può notare il comportamento non è identico, se consideriamo i valori numerici e non i segni.
Per riprodurre il comportamento dell’operatore spaceship in PHP 5 potremmo immaginare di scrivere una funzione simile a questa:
|
function spaceship($a,$b) { if( $a < $b ) return -1; if( $a == $b ) return 0; if( $a > $b ) return 1; } |
Applicandola all’elenco precedente:
|
echo "a <=> b : " . spaceship($a, $b) . "<br>"; echo "mario <=> maria : " . spaceship("marib", "maria") . "<br>"; echo "marii <=> mario : " . spaceship("marii", "mario") . "<br>"; echo "maria <=> mario : " . spaceship("maria", "mario") . "<br>"; echo "mario <=> luigi : " . spaceship("mario", "luigi") . "<br>"; echo "1 <=> 2 : " . spaceship(1, 2) . "<br>"; echo "2 <=> 1 : " . spaceship(2, 1) . "<br>"; echo "3 <=> 3 : " . spaceship(3, 3) . "<br>"; |
Avremo gli stessi risultati di <=>
in PHP 7:
a <=> b : 0
mario <=> maria : 1
marii <=> mario : -1
maria <=> mario : -1
mario <=> luigi : 1
1 <=> 2 : -1
2 <=> 1 : 1
3 <=> 3 : 0
Immaginiamo dunque di voler scrivere una funzione di ordinamento per un array. In PHP 5 avremmo potuto scrivere:
|
function ordina($a,$b) { if( $a < $b ) return -1; if( $a == $b ) return 0; if( $a > $b ) return 1; } $elenco = array(3,4,1,7,8,2); usort($elenco,"ordina"); print_r( $elenco ); |
Mentre in PHP 7 possiamo sintetizzare con:
|
function ordina($a, $b) { return $a <=> $b; } $elenco = array(3,4,1,7,8,2); usort($elenco,"ordina"); print_r( $elenco ); |
Entrambi i casi produrranno un output ordinato come il seguente:
Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 7 [5] => 8 )
Inutile dire che per entrambi gli esempi avremmo potuto incorporare direttamente una funzione anonima dentro ad usort
in questo modo:
|
usort($elenco,function($a, $b) { return $a <=> $b; }); |
Infine nel PHP 5 l’uso dell’operatore spaceship produrrebbe un errore di questo tipo:
Parse error: syntax error, unexpected '>' in /index.php on line 16
3. Null Coalesce Operator
In PHP 7 viene introdotto il null coalescing operator ??
, proseguendo così le implementazioni sull’if lineare, già introdotte nel PHP 5.3 con l’Elvis operator ?:
.
In generale sappiamo che possiamo scrivere un’istruzione con if lineare (o operatore ternario) di questo tipo:
|
$valore = 1 > 2 ? true : false; |
Questa istruzione equivale a scrivere:
|
if( 1 > 2 ) { $valore = true; } else { $valore = false; } |
In generale questa istruzione può essere utilizzata per riassegnare la medesima variabile in uno dei due casi. Per esempio immaginiamo di voler scrivere:
|
$x = 1; $y = 2; $valore = $x ? $x : $y; echo $x; |
Con l’Elvis operator possiamo sintetizzare la scrittura nel modo seguente:
|
$x = 1; $y = 2; $valore = $x ?: $y; echo $x; |
Quello che viene assegnato è l’argomento del test. Se l’argomento del test comprende operatori logici viene assegnato il risultato dell’operazione logica.
Per capire meglio immaginiamo di voler trovare il più grande valore tra quelli dati (ci sarebbero altri mille modi, ma per lo scopo dell’esercizio prendiamo questo):
|
$elenco = array(3,4,1,7,8,2); foreach( $elenco as $v ) { $max = $max > $v ? $max : $v; } echo $max; |
Il risultato di questa operazione darà ovviamente come risultato 8.
Se lo riscrivessimo con l’Elvis operator potremmo avere qualcosa del genere:
|
$elenco = array(3,4,1,7,8,2); foreach( $elenco as $v ) { $max = $max > $v ?: $v; } echo $max; |
Purtroppo però il risultato non sarebbe più 8, bensì 1, e questo indipendentemente dal fatto che 1 esista nell’array. E’ 1 perché 1 è true, il risultato di una delle operazioni di test. (il penultimo massimo è 8, quindi 8 è maggiore di 2? sì, vero, quindi 1!)
Praticamente serve solo quando il risultato che vogliamo valutare è tra true o false, stringa nulla o meno, ecc.
A questo si aggiunge il null coalescing operator che integra il controllo della nullità della variabile, sostituendosi alla funzione isset. Immaginiamo di avere:
|
if( isset( $_GET['valore'] ) ) { $valore = $_GET['valore']; } else { $valore = null; } echo $valore; |
Questo può diventare ora:
|
$valore = $_GET['valore'] ?? null; echo $valore; |
Il comportamento è identico. Il risultato è che le seguenti due scritture sono fondamentalmente uguali, eccetto per un dettaglio:
|
$valore = $_GET['valore'] ?? 42; echo $valore; $valore = $_GET['valore'] ?: 42; echo $valore; |
La prima non produce una notice, mentre la seconda ci segnala qualcosa del tipo:
Notice: Undefined index: valore in index.php on line 9
Facciamo un altro esempio con entrambe:
|
class Studente { var $nome; } $valore = $studente->nome ?? "Mario"; echo $valore; $valore = $studente->nome ?: "Mario"; echo $valore; |
La prima scrittura restituisce Mario senza ulteriori errori, l’oggetto non è dichiarato, quanto l’attributo; la seconda produce ancora due notice:
Notice: Undefined variable: studente in index.php on line 15
Notice: Trying to get property 'nome' of non-object in index.php on line 15
A questo punto potremmo anche scrivere:
|
$studente = $studente ?? new Studente(); |
Per dichiarare l’oggetto laddove inesistente, mentre la seguente scrittura produrrebbe sempre una notice:
|
$studente = $studente ?: new Studente(); |
A questo punto potremmo scrivere altre due equivalenze, dal punto di vista del risultato:
|
$valore = ($studente ?? new Studente())->nome ?? "Mario"; echo $valore; $valore = ($studente ?? new Studente())->nome ?: "Mario"; echo $valore; |
Ovviamente possiamo sfruttare l’operatore per altre aggregazioni del tipo:
|
$a = null; $b = null; $c = 42; $d = null; $e = 77; $valore = $a ?? $b ?? $c ?? $d ?? $e; echo $valore; |
Dove il risultato è 42, ovvero il primo valore non null della sequenza.
4. Virgola finale negli argomenti di funzione
Adesso è possibile dimenticarsi una virgola dopo l’ultimo argomento di una funzione, senza che il PHP si agiti per questo 🙄
Mentre sia nel PHP 5 che nel PHP 7 la seguente dicitura non provocava problemi:
|
$elenco = array(-3,-4,-1,-7,-8,-2,); |
Provare a fare lo stesso con una funzione, che accetti argomenti predefiniti o meno, provoca un errore:
|
function pippo($a,$b) { return null; } pippo(1,2,); function indefinita(...$args) { return count($args); } indefinita(1,2,3,); |
Nel PHP 5 entrambe le funzioni avrebbero prodotto un errore del tipo:
Parse error: syntax error, unexpected ')' in index.php on line 12
Personalmente non la trovo un’innovazione strepitosa, ma va bene, adesso sappiamo che c’è.
5. Migrazione a PCRE2
Il PHP utilizza in generale PCRE per l’interpretazione delle espressioni regolari. Nel PHP 7.3 è stato finalmente introdotto l’utilizzo del PCRE2 che ha aspetti un po’ più stringenti e severi sulla sintassi. Facciamo un esempio, immaginando di avere una stringa dalla quale vogliamo trovare tutte le parole, compreso il simbolo – (meno) e il . (punto); nel PHP 5 entrambe le seguenti scritture sarebbero state valide:
|
$testo = "- Atëherë po ta shpjegoj. Njeriu më i lumtur i kësaj bote do ta përdorte Pasqyrën e Dëshirave si një pasqyrë të zakonshme, domethënë që, duke u parë në të, do ta shihte veten bash ashtu siç është. Fillove të kuptosh?"; preg_match_all('/[\w-.]+/', $testo, $matches); print_r( $matches ); preg_match_all('/[\w\-.]+/', $testo, $matches); print_r( $matches ); |
Soprassediamo sul fatto che i caratteri speciali verranno saltati e considerati alla stregua di spazi, ma il risultato sarà qualcosa di simile a:
Usando la prima espressione regolare all’interno di un programma come il Notepad++ avremmo ottenuto un errore:
Mentre la seconda è valida. Adesso anche nel PHP 7.3 la seconda espressione è l’unica valida, mentre la prima non funziona producendo un errore del tipo:
Warning: preg_match_all(): Compilation failed: invalid range in character class at offset 3 in index.php on line 10
Giusto per curiosità, se si volessero prendere anche i caratteri speciali potremmo utilizzare la seguente espressione regolare:
|
preg_match_all('/[\w\x{c0}-\x{ff}\-.]+/', $testo, $matches); |
6. Unicode Codepoint Escape Syntax
Visto che si parla di stringhe facciamo un piccolo appunto a proposito dei riferimenti ai caratteri Unicode. Adesso è possibile specificarli direttamente nelle stringhe di testo con la sintassi \u{XXX}
. Maggiori approfondimenti si possono trovare qui sul sito ufficiale. Questo significa che scrivendo:
|
echo "\u{202E}Testo inverso"; |
Nel PHP 5 vedremo la stringa tale e quale:
\u{202E}Testo inverso
Mentre nel PHP 7 vedremo:
Testo inverso
Il carattere in questione è il U+202E RIGHT-TO-LEFT OVERRIDE
Si possono usare tutti i caratteri unicode, anche quelli associati agli smile, per cui echo "\u{1F602}";
produrrà ?
7. Chiamata di errore nella codifica JSON
Adesso è finalmente possibile forzare il JSON a sollevare un errore in caso di stringhe non (de)codificabili. E’ possibile infatti scrivere nel modo seguente:
|
json_encode($dati, JSON_THROW_ON_ERROR); json_decode("stringa JSON non valida", null, 512, JSON_THROW_ON_ERROR); |
Senza l’aggiunta di JSON_THROW_ON_ERROR
non ci sarebbe nessuna segnalazione di errore. Grazie a tale istruzione otteniamo invece il seguente errore fatale:
Fatal error: Uncaught JsonException: Syntax error in index.php:8 Stack trace: #0 index.php(8): json_decode('stringa JSON no...', NULL, 512, 4194304) #1 {main} thrown in index.php on line 8
8. Exceptions per i fatal errors
A proposito di errori, finalmente è stato migliorato il sistema di cattura delle eccezioni con try… catch. Primo era sostanzialmente, per farla breve, necessario chiamare throw new Exception();
per sollevare un’eccezione. Adesso è possibile intercettare anche le eccezioni di sistema. Nel caso precedente l’eccezione è intercettabile con:
|
try { json_encode($dati, JSON_THROW_ON_ERROR); json_decode("stringa JSON non valida", null, 512, JSON_THROW_ON_ERROR); } catch( Exception $e ) { echo "errore: " . $e->getMessage(); } |
Come altro esempio immaginiamo di chiamare una funzione non esistente:
|
try { funzione(); } catch( Error $e ) { echo "errore: " . $e->getMessage(); } |
Nel dobbiamo usare Error per intercettare l’errore, e mentre nel PHP 5 avremmo comunque avuto il seguente output:
Fatal error: Call to undefined function funzione() in index.php on line 9
Mentre nel PHP 7 avremmo il seguente valore, prodotto dal nostro echo:
errore: Call to undefined function funzione()
9. Classi anonime
Tra le varie novità vi è la possibilità di implementare classi anonime. Cominciamo dall’esempio più semplice:
|
$oggetto = new class() { var $nome = "Mario"; }; echo $oggetto->nome; |
Nel PHP 5 questo produrrebbe un errore del tipo:
Parse error: syntax error, unexpected 'class' (T_CLASS) in index.php on line 7
Alla classe anonima possono essere passati anche degli argomenti per il costruttore, nel modo seguente:
|
$oggetto = new class("Luigi") { var $nome = "Mario"; function __construct($nome) { $this->nome = $nome; } }; echo $oggetto->nome; |
L’output invece che Mario
sarà Luigi
. Le classi anonime possono anche estendere altre classi, come ad esempio:
|
class Studente { var $cognome = "Rossi"; } $oggetto = new class("Luigi") extends Studente { var $nome = "Mario"; function __construct($nome) { $this->nome = $nome; } }; echo $oggetto->cognome; |
Oppure implementarle:
|
interface Studente { function stampaNome(); } $oggetto = new class("Luigi") implements Studente { var $nome = "Mario"; function __construct($nome) { $this->nome = $nome; } function stampaNome() {} }; echo $oggetto->nome; |
Senza implementare il metodo stampaNome()
avremmo il seguente errore:
Fatal error: Class class@anonymous contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Studente::stampaNome) in index.php on line 11
10. Dichiarazione del tipo di dato restituito
Il PHP 7 introduce la possibilità di forzare il tipo di dato restituito da una funzione. Immaginiamo di voler scrivere una funzione somma che restituisca solamente valori interi. Nella maniera classica avremmo scritto:
|
function somma($a,$b) { return $a + $b; } echo somma(2.5,3); |
Passando come argomento 2.5 e 3 il risultato sarebbe stato 5.5, un float. Per forzare l’output di un intero avremmo potuto scrivere:
|
function somma($a,$b) { return (int) ($a + $b); } echo somma(2.5,3); |
In PHP 7 è possibile anche scrivere:
|
function somma($a,$b) : int { return $a + $b; } echo somma(2.5,3); |
Sintassi che nel PHP 5 avrebbe prodotto il seguente errore:
Parse error: syntax error, unexpected ':', expecting '{' in index.php on line 7
11. Metodo is_countable()
In PHP 7 è possibile verificare se un oggetto sia “conteggiabile” ovvero sia del tipo Countable. Immaginiamo la seguente serie di istruzioni:
|
class Studenti implements Countable { private $conteggio = 5; public function count() { return $this->conteggio; } } $studenti = new Studenti(); if( is_countable($studenti) ) { echo count($studenti); } |
Essendo Studenti conteggiabile viene stampato il conteggio. Nel PHP 5 avremmo ovviamente avuto un errore del tipo:
Fatal error: Call to undefined function is_countable() in index.php on line 19
Facciamo un altro esempio di funzione implementabile su Countable e dove ci può tornare utile il nuovo metodo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
class Studenti implements Countable { private $studenti = array(); private $conteggio = 0; public function count() { return $this->conteggio; } public function add($nome) { $this->studenti[] = $nome; $this->conteggio++; } public function get($index) { return $this->studenti[$index]; } } $studenti = new Studenti(); $studenti->add("Squall"); $studenti->add("Zell"); $studenti->add("Rinoa"); if( is_countable($studenti) ) { for( $i = 0; $i < count($studenti); $i++ ) { echo $studenti->get($i); } } |
In questo caso non abbiamo bisogno di chiamare la funzione count()
all’interno di public function count()
, ma possiamo tenere traccia della quantità di voci aggiunte al vettore $studenti tramite un contatore interno.
12. Primi e ultimi valori e chiavi di un array
Immaginiamo di avere un array di questo tipo:
|
$valori = array("a" => 1, "b" => 10, "c" => 100); |
Alla maniera precedente avremmo potuto scrivere:
|
echo "primo valore: " . reset($valori) . "<br>"; echo "prima chiave: " . key($valori) . "<br>"; echo "ultimo valore: " . end($valori) . "<br>"; echo "ultima chiave: " . key($valori) . "<br>"; |
In questo modo avremmo ottenuto un output del genere:
primo valore: 1
prima chiave: a
ultimo valore: 100
ultima chiave: c
Questo implica anche che stiamo spostando il cursore nella posizione attuale dell’array. Se provassimo a scrivere:
|
echo "primo valore: " . reset($valori) . "<br>"; echo "ultimo valore: " . end($valori) . "<br>"; echo "prima chiave: " . key($valori) . "<br>"; echo "ultima chiave: " . key($valori) . "<br>"; |
Il risultato non sarebbe corretto e avremmo:
primo valore: 1
ultimo valore: 100
prima chiave: c
ultima chiave: c
Quindi questa scrittura sarebbe stata impossibile. Ora, con il PHP 7.3 è possibile scrivere nel modo seguente:
|
echo "primo valore: " . reset($valori) . "<br>"; echo "ultimo valore: " . end($valori) . "<br>"; echo "prima chiave: " . array_key_first($valori) . "<br>"; echo "ultima chiave: " . array_key_last($valori) . "<br>"; |
Le funzioni proposte sono attualmente: array_key_first(), array_key_last() and array_value_first(), array_value_last() anche se sono state introdotte, nel PHP 7.3 solamente quelle riguardanti le chiavi. Maggiori dettagli sulla questione qui: PHP RFC: array_key_first(), array_key_last() and array_value_first(), array_value_last()
13. Il metodo Closure->call
Nel PHP 7 viene introdotta una semplificazione nell’atto di binding delle closure.
Se sembra che tutti si stia dicendo cose a caso vediamo un esempio:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
class Scuola { private $studenti = array(); public function print() { foreach( $this->studenti as $s ) echo "$s<br>"; } } $closure = function( $studente ) { $this->studenti[] = $studente; }; $scuola = new Scuola(); $closure->call($scuola, "Mario Rossi"); $closure->call($scuola, "Luigi Verdi"); $scuola->print(); |
Notiamo che la funzione scritta su $closure
va ad integrare la classe, potendo interagire con tutte le sue variabili interne anche se private.
14. Prestazioni migliorate
Infine il PHP 7 introduce notevoli implementazioni in termini di performance. Di seguito riporto i risultati di benchmark ottenuti grazie a benchmark-php su un medesimo server, ma con due diverse versioni di PHP. In particolare la versione PHP 7.3 introduce notevole stabilità nell’esecuzione e nel mantenimento delle performance.
PHP 7.3.5 |
PHP 5.6.40 |
|
|