Le novità di PHP 7.3

PHP 7.3 sta bussando alle nostre porte e porta con sé nuove utili funzionalità, deprecazioni e un buon numero di correzioni di bug. Questa versione è interamente dedicata agli sviluppatori. L’attuale versione RC 4 è stata rilasciata il 25 ottobre 2018, ed è assolutamente puntuale con il programma di PHP 7.3 (ma ad oggi 4 novembre è meglio tenere presente che questa versione al momento non dovrebbe essere utilizzata negli ambienti di produzione).

Ecco quindi una veloce panoramica delle funzionalità e delle modifiche che ritengo più rilevanti.

L’elenco completo delle funzionalità, delle modifiche e delle correzioni di bug si trovano in ogni caso a questi link: PHP 7.3 upgrade notes e PHP 7.3 Requests For Comments.

Sintassi Heredoc e Nowdoc flessibili

Questa è probabilmente una delle modifiche più rilevanti in arrivo con PHP 7.3, e penso che meriti un po’ di attenzione in più. Quindi, prima di passare alle modifiche apportate da PHP 7.3 a heredoc e nowdoc, fornirò una rapida panoramica di questa utile funzionalità core. Se conosci già bene le sintassi di nowdoc ed heredoc puoi saltare oltre…

La sintassi heredoc fornisce un modo per aggiungere una grande quantità di testo senza la necessità di effettuare l’escape di cose come le doppie virgolette. Un heredoc inizia con <<< seguito da un marker e termina con lo stesso marker seguito da un punto e virgola. Ecco un esempio:

print <<<EOT
Heredoc text behaves just like a double-quoted string, without the double quotes.
EOT;

Un nowdoc si comporta in modo molto simile ad un heredoc, ma con alcune eccezioni:

  • L’identificatore è racchiuso tra virgolette singole (<<<'EOT')
  • Nessun parsing viene eseguito all’interno di un nowdoc

Ecco un esempio di nowdoc:

print <<<'EOT'
Nowdocs are to single-quoted strings what heredocs are to double-quoted strings.
EOT;

Heredoc e nowdoc condividono le stesse regole di utilizzo del marker di chiusura:

  • Il marker di chiusura deve iniziare nella prima colonna della linea
  • Il marker deve seguire le stesse regole di denominazione di qualsiasi altra etichetta in PHP: deve contenere solo caratteri alfanumerici e caratteri di sottolineatura e deve iniziare con un carattere non numerico o con un underscore.

Il manuale PHP avverte:

È molto importante notare che la linea con l’identificatore di chiusura non deve contenere altri caratteri, eccetto un punto e virgola (;). Ciò significa in particolare che l’identificatore non può essere indentato e non possono esserci spazi o tabulazioni prima o dopo il punto e virgola. È anche importante comprendere che il primo carattere prima dell’identificatore di chiusura deve essere una nuova riga, come definita dal sistema operativo locale. Questo è \n sui sistemi UNIX, macOS compreso. Il delimitatore di chiusura, inoltre, deve essere seguito da una nuova riga.

Per farla breve, in PHP 7.2:

  • Il marker di chiusura non può essere indentato
  • La linea con il marker di chiusura non può contenere caratteri come spazi o tabulazioni
  • Il primo carattere prima del marker di chiusura deve essere una nuova riga
  • Il marker di chiusura deve essere seguito da una nuova riga

È chiaro, quindi, che le sintassi di heredoc e nowdoc sono piuttosto restrittive, ma PHP 7.3 potrebbe cambiare un po’ le cose con i miglioramenti illustrati di seguito.

 

1. Possibilità di Indentare il Marker di Chiusura e di Eliminare gli Spazi Bianchi Iniziali

Con PHP 7.3 ci è permesso indentare il marker di chiusura e possiamo tranquillamente scrivere il seguente codice:

class foo {
    public $bar = <<<EOT
        bar
    EOT;
}

L’indentazione del marker di chiusura stabilisce il numero di spazi bianchi (o tabulazioni) che saranno eliminati da ogni linea del corpo del testo. Ma attenzione: il marker di chiusura non deve mai essere indentato al di là di qualsiasi altra linea del corpo del testo.

Prendiamo il seguente codice:

class foo {
    public $bar = <<<EOT
    bar
        EOT;
}

Questo codice emetterebbe il seguente parse error:

Parse error: Invalid body indentation level (expecting an indentation at least ...) in %s on line %d

L’eliminazione delle tabulazioni e degli spazi bianchi ci permette di indentare il corpo di heredoc e nowdoc allo stesso livello del codice, e senza avere spazi bianchi non necessari prima di ogni riga.

Possiamo utilizzare per l’indentazione sia le tabulazioni che gli spazi, ma non ci è permesso di utilizzarli mescolati. Ciò significa che dobbiamo usare gli stessi caratteri di indentazione per il marker di chiusura e per qualsiasi linea del corpo. In caso di caratteri di indentazione diversi, ci dovremo aspettare un diverso tipo di errore di parsing (indentazione non valida).

 

2. Rimozione del Requisito della Nuova Riga Finale per il Marker di Chiusura

Attualmente, una nuova riga deve seguire il marcatore per terminare un heredoc/nowdoc. PHP 7.3 dovrebbe cambiare questo stato di cose e ci permetterà di terminare l’heredoc/nowdoc sulla stessa riga. Ecco un esempio della RFC:

Sintassi PHP 7.2 valida:

$values = [<<<END
a
b
c
END
, 'd e f'];

Sintassi PHP 7.3 valida:

$values = [<<<END
a
b
c
END, 'd e f'];

Ad ogni modo, bisogna fare molta attenzione quando si sceglie il nome del marcatore perché “occasionalmente” potrebbe comparire un errore nel caso in cui questo corrisponda a una parola usata nel corpo dell’heredoc/nowdoc (per un approfondimento RFC e GitHub).

Entrambe le proposte sono state approvate con più di 2/3 dei voti.

RFC PHP 7.3

Altre Risorse

 
Possibilità di inserire una virgola finale nelle chiamate di funzioni

Le virgole finali sono delle virgole aggiunte ad un elenco di elementi, parametri o proprietà e sono utili in contesti in cui nuovi valori vengono aggiunti di frequente, perché impediscono errori dovuti a una virgola mancante. In PHP le virgole finali sono consentite negli array e, a partire da PHP 7.2, sono consentiti nei namespace raggruppati.

A partire da PHP 7.3, le virgole finali sono consentite nelle dichiarazioni di funzione. Le funzioni variadiche offrono un esempio di contesto in cui le virgole finali sono estremamente utili:

foo(
    $bar,
    $baz,
);

Possiamo utilizzare una virgola finale quando stiamo creando un array con compact(), per restituire una stringa formattata con sprintf(), oppure quando si combinano degli array:

$newArray = array_merge(
    $arrayOne,
    $arrayTwo,
    ['foo', 'bar'],
);

Inoltre, le virgole finali possono essere utili per il debug:

var_dump(
    $foo,
    $bar,
    $baz,
);

E sono davvero potenti con unset() ed isset():

unset(
    $foo,
    $bar,
    $baz,
);

isset(
    $foo,
    $bar,
    $baz,
);

Le virgole finali saranno consentite anche nelle chiamate di metodi e nelle enclosure.

Nota: questa modifica influirà solo sulle chiamate di funzione. La sintassi delle dichiarazioni di funzione non cambierà. Inoltre, le virgole indipendenti, le virgole multiple e le virgole iniziali non saranno consentite.

Ulteriori esempi possono essere trovati sulla pagina della RFC. Questa RFC è stata approvata con 30 voti a favore e 10 contrari.

PHP 7.3 RFC

JSON_THROW_ON_ERROR

Una delle funzionalità più apprezzate che arriveranno con PHP 7.3 riguarda una nuova modalità di gestione degli errori JSON. Questa non è una funzionalità core, ma un’aggiunta all’estensione JSON che cambierà il comportamento in caso di errore di json_decode() e json_encode().

Attualmente, json_decode() restituisce null in caso di errore, ma anche null può essere un risultato valido. Questo potrebbe essere fonte di confusione, perché:

È possibile sapere se si è verificato un errore solo invocando json_last_error() o json_last_error_msg(), che restituiscono lo stato di errore globale rispettivamente nei moduli leggibili dalla macchina e leggibili dall’uomo. – PHP RFC

json_encode() restituisce FALSE in caso di errore. Questo è più chiaro perché c’è uno specifico valore per l’errore. Ad ogni modo, entrambe le funzioni non interrompono l’esecuzione del programma in caso di errore, né generano alcun avviso.

Detto questo, ecco la proposta per PHP 7.3:

Questa RFC propone invece di aggiungere un nuovo valore per l’option flag di json_decode() e json_encode()JSON_THROW_ON_ERROR. Quando viene passato questo flag, viene modificato il comportamento di queste funzioni in caso di errore. Lo stato di errore globale non viene modificato e, se si verifica un errore che avrebbe potuto impostarlo in altro modo, queste funzioni generano invece una JsonException con il messaggio e il codice impostato allo stesso valore che sarebbe risultato altrimenti con json_last_error() e json_last_error_msg().

Ecco un esempio che mostra un modo semplice per emettere un errore JSON:

try {
    json_decode("{", false, 512, JSON_THROW_ON_ERROR);
}
catch (\JsonException $exception) {
    echo $exception->getMessage(); // echoes "Syntax error"
}

L’emissione di un’eccezione in caso di errore dovrebbe assicurare diversi vantaggi che sono elencati nella RFC.

Nota: un parametro di profondità non valido passato a json_decode() genera un avviso e restituisce NULL. Questo comportamento non sarà influenzato da JSON_THROW_ON_ERROR. Allo stesso modo, gli errori di parsing dei parametri non sono influenzati da JSON_THROW_ON_ERROR e continueranno a generare avvisi.

Questa proposta è stata approvata con 23 voti a 0.

RFC PHP 7.3

Altre Risorse

 
Assegnazione Per Riferimento in list()

Cosa Significa Assegnazione Per Riferimento?

Consiamo la seguente riga:

$b = &$a;

Qui $b prende il valore di $a, ma quel valore non viene copiato da $a a $b. In PHP possiamo assegnare un valore per riferimento, nel senso che due variabili possono puntare agli stessi dati, e ogni modifica ad una qualsiasi delle variabili influisce sui dati originali. Ecco un esempio tratto dal manuale PHP:

<?php
$a = 3;
$b = &$a; // $b is a reference to $a

print "$a\n"; // prints 3
print "$b\n"; // prints 3

Ora, cambiamo il valore di $a:

$a = 4; // change $a

print "$a\n"; // prints 4
print "$b\n"; // prints 4 as well, since $b is a reference to $a, which has been changed

Cosa È il Costrutto list() e Come Cambia con PHP 7.3

Il costrutto di linguaggio list() può essere usato per “assegnare variabili come se fossero in un array”, ma con list() non è attualmente possibile assegnare per riferimento i valori delle variabili.

PHP 7.3 dovrebbe cambiare questo stato di cose, permettendoci di assegnare variabili per riferimento anche con il costrutto list(), come mostrato nel seguente esempio:

$array = [1, 2];
list($a, &$b) = $array;

Che è la stessa cosa di:

$array = [1, 2];
$a = $array[0];
$b =& $array[1];

Il vantaggio di questa proposta è che ora potremmo assegnare più variabili per riferimento, cosa che non è consentita al momento. Altri esempi sono disponibili sulla RFC. Questa proposta è passata con 17 voti contro 7.

RFC PHP 7.3

Altre Risorse

 
La Funzione is_countable

Un’altra utile funzionalità in arrivo con PHP 7.3 è la funzione is_countable(). Fino a PHP 7.2, ci viene dato un errore quando tentiamo di contare qualcosa che non è numerabile con la funzione count(). Per questo motivo, al fine di evitare un warning, siamo costretti ad aggiungere il seguente codice:

if (is_array($foo) || $foo instanceof Countable) {
    // $foo is countable
}

Questa RFC propone la funzione is_countable(), che restituisce true se la variabile data è un array o una variabile numerabile, false in caso contrario. Quindi, il codice di sopra potrebbe essere modificato come segue:

if (is_countable($foo)) {
    // $foo is countable
}

Questa proposta è passata con 25 voti a 0.

RFC PHP 7.3

Altre Risorse

 
array_key_first(), array_key_last()

Attualmente, possiamo recuperare la prima e l’ultima chiave di un array usando le funzioni reset()end() e key(). Sfortunatamente, con queste funzioni, non c’è modo di recuperare il primo o l’ultimo indice di un array senza cambiarne lo stato interno. Altre opzioni in genere riducono la leggibilità e le prestazioni del codice.
Questa proposta cambierebbe questo scenario aggiungendo due nuove funzioni al core di PHP:

  • array_key_first()
  • array_key_last()

A partire da PHP 7.3, array_key_first() e array_key_last() consentono di recuperare la prima e l’ultima chiave di un dato array senza influenzare il puntatore interno dell’array. Queste nuove funzioni ci dovrebbero consentire di scrivere codice meno complesso e in alcuni casi di evitare errori. Si legga la RFC per ulteriori informazioni e diversi esempi.

Le funzioni array_key_first() e array_key_last() sono stati approvate con 18 voti contro 14.

Nota: l’RFC originale proponeva altre due funzioni, array_value_first() e array_value_last(), che sono state proposte in una votazione separata, ma non sono state approvate e non diventeranno parte del core di PHP.

RFC PHP 7.3

Altre Risorse

 
Miglioramenti all’Hash delle Password di Argon2

Argon2 è un algoritmo di hash implementato in PHP 7.2 come alternativa all’algoritmo Bcrypt. PHP 7.2 ha introdotto la costante PASSWORD_ARGON2I, disponibile per essere utilizzata nelle funzioni password_*:

password_hash('password', PASSWORD_ARGON2I);

Dopo la sua prima implementazione, è stata aggiunta una nuova variante di Argon2, quindi, al momento in cui scriviamo, Argon2 è disponibile nelle seguenti tre varianti:

  • Argon2d massimizza la resistenza agli attacchi di cracking della GPU. È più veloce e utilizza l’accesso alla memoria in base ai dati.
  • Argon2i utilizza l’accesso alla memoria indipendente dai dati, che è una cosa preferibile per l’hashing delle password. È più lento in quanto effettua più passaggi sulla memoria per proteggere dagli attacchi di tradeoff.
  • Argon2id è una versione ibrida che combina l’approccio di Argon2i per il primo passaggio sulla memoria e l’approccio Argon2d per i passaggi successivi.

Argon2id è consigliato su Internet, tranne quando ci sono buone ragioni per preferire specificamente un’altra variante.

La nuova RFC propone l’implementazione di Argon2id nelle funzioni password_* con la nuova costante PASSWORD_ARGON2ID:

password_hash('password', PASSWORD_ARGON2ID);

L’implementazione è identica ad Argon2i e accetta gli stessi fattori di costo:

  • Un memory cost che definisce il numero di KiB da consumare durante l’hashing (i valori predefiniti sono 1<<10, o 1024 KiB, o 1 MiB)
  • Un time cost che definisce il numero di iterazioni dell’algoritmo di hashing (il valore predefinito è 2)
  • Un fattore di parallelismo, che imposta il numero di thread paralleli che verranno utilizzati durante l’hashing (il valore predefinito è 2)

Ecco un esempio di codice:

$options = ['memory_cost' => 1<<11, 'time_cost' => 4, 'threads' => 2];
password_hash('password', PASSWORD_ARGON2ID, $options);

Ulteriori informazioni ed esempi sono disponibili nella RFC.

RFC PHP 7.3

Altre Risorse

 
Deprecazioni

Le seguenti funzioni/funzionalità saranno deprecate con PHP 7.3 e rimosse non più tardi di PHP 8.0.

Deprecazione e Rimozione di image2wbmp()

La funzione image2wbmp() genera o salva una versione WBMP di una determinata immagine. Questa funzione richiede tre argomenti: una risorsa immagine, un nome file (il percorso del file salvato) e un colore di primo piano.
A partire da PHP 5.0, questa funzione è identica a imagewbmp(), quindi questa RFCpropone di deprecarla e rimuoverla.
A partire da PHP 7.3, ogni chiamata a image2wbmp() emette un avviso di deprecazione. Dopo la rimozione, ogni chiamata genera un errore fatale.

RFC PHP 7.3

Deprecazione e Rimozione delle Costanti Case-Insensitive

PHP attualmente supporta sia costanti case-sensitive che case-insensitive. Comunque, le costanti case-insensitive sono supportate ma considerate soggette a incoerenze nelle funzionalità e complesse nell’utilizzo.
Questa proposta inizia con la seguente premessa:

  • Le costanti di classe sono sempre case-sensitive
  • Le costanti globali dichiarate con const sono sempre case-sensitive
  • Le costanti definite con define() sono case-sensitive di default

Inoltre, il PHP Language Reference afferma esplicitamente:

Una costante è case-sensitive per impostazione predefinita. Per convenzione, gli identificatori di costanti sono sempre in maiuscolo.

Detto questo, questo RFC propone le seguenti modifiche:

  • Deprecazione delle chiamate a define() con il terzo parametro impostato a true – PHP 7.3
  • Deprecazione dell’accesso a costanti case-insensitive con lettere diverse dalla dichiarazione (con l’eccezione di truefalse e null) – PHP 7.3
  • Rimozione della possibilità di dichiarare costanti case-insensitive – PHP 8.0
  • Conversione di truefalse e null da costanti a lettere speciali in parole chiave riservate – PHP 8.0

RFC PHP 7.3

Deprecate and Remove Case-Insensitive Constants.

Ulteriori Deprecazioni per PHP 7.3

Ecco una rapida lista di funzionalità deprecate in PHP 7.3. Non è una lista esaustiva, sono solo le proposte di deprecazione che considero personalmente più rilevanti. Per un elenco completo, si veda Deprecations for PHP 7.3.

Alias ​​della funzione mbstring non documentati: esiste un numero di alias della funzione mbstring non documentati che sono duplicazioni di funzioni equivalenti che utilizzano il prefisso mb_. Ad esempio, mbereg è un alias di mb_ereg.
Tutte queste funzioni sono contrassegnate come deprecate e viene generata una notifica di deprecazione quando vengono rilevate durante la compilazione.

Funzioni di ricerca di stringhe con needle intero: queste funzioni di solito funzionano su needle stringa. Se viene dato un needle non stringa, questo viene convertito in un numero intero e applicato come il valore ordinale di un carattere (per un approfondimento si legga il manuale PHP). Ecco un esempio tratto dalla RFC:

$str = "There are 10 apples";
var_dump(strpos($str, "10")); // int(10)
var_dump(strpos($str, 10));   // bool(false)

Questo è considerato complicato e causa di problemi imprevedibili perché il tipo può cambiare con l’origine dei dati dell’utente. Per questo motivo, l’RFC propone l’emissione di un avviso di deprecazione se un needle non stringa viene passato ad una delle seguenti funzioni:

  • strpos
  • strrpos
  • stripos
  • strripos
  • strstr
  • strchr
  • strrchr
  • stristr

In PHP 8.0, l’avviso di deprecazione dovrebbe essere rimosso e i needle dovrebbero essere convertiti automaticamente in stringhe.

Funzione fgetss() e filtro di flusso string.strip_tagsfgetss() e string.strip_tags eliminano i tag da un flusso durante la lettura del flusso stesso. Sia la funzione che il filtro espongono la funzionalità di strip_tags() rendendo più complessa l’implementazione di questa funzione, in quanto è richiesta una macchina a stati di flusso. Inoltre, la RFC sottolinea un altro inconveniente di queste funzioni:

D’altra parte, queste funzioni sembrano essere di scarsissima utilità. La stessa funzione strip_tags(), a causa delle sue limitazioni e dei bug noti, ha già pochissime applicazioni legittime. Non è necessario fornire supporto nativo per l’applicazione dello streaming.

Quindi l’RFC propone di contrassegnare fgetss()gzgetss() e SplFileObject::fgetss() come deprecati.