PHP 8: nuove funzionalità e breaking changes

PHP 8: nuove funzionalità e breaking changes

Manca poco più di un mese al rilascio di PHP 8. È una nuova major release, il che significa che introdurrà alcune modifiche sostanziali. Scopriamole insieme.

Questo articolo è stato scritto molto tempo fa. Potrebbe essere stato superato dalla tecnologia.

Manca poco più di un mese al rilascio di PHP 8, pianificato per il 26 novembre 2020. È una nuova major release, il che significa che introdurrà alcune modifiche sostanziali, oltre a molte nuove funzionalità e miglioramenti delle prestazioni. In questo momento PHP 8 è in “feature freeze”, il che significa che non è più possibile aggiungere nuove funzionalità prima del rilascio.

A causa delle modifiche strutturali, c'è una discreta possibilità che ci sia da apportare qualche modifica al codice precedente per farlo funzionare su PHP 8. Se ti sei tenuto aggiornato con le ultime versioni però, l'aggiornamento non dovrebbe esserlo complicato, poiché la maggior parte delle modifiche di rilievo erano state deprecate prima nelle versioni 7.*. Alla fine di questo post vedremo le più impattanti.

Oltre alle modifiche sostanziali, PHP 8 offre anche un bel set di nuove funzionalità come il compilatore JIT, gli unit types, gli attributi ed altro.

Nuove caratteristiche

Cominciamo con tutte le nuove funzionalità, è un bel elenco!

Union types

Data la natura tipizzata dinamicamente di PHP, ci sono molti casi in cui gli union types possono essere utili. Gli union types sono una raccolta di due o più tipi di dato che indicano che uno di questi può essere utilizzato. Mentre fino ad oggi usavamo le annotazioni phpdoc:

class Number {
    /**
     * @var int|float \$number
     */
    private \$number;
 
    /**
     * @param int|float \$number
     */
    public function setNumber(\$number) {
        \$this->number = \$number;
    }
 
    /**
     * @return int|float
     */
    public function getNumber() {
        return \$this->number;
    }
}

Ora

class Number {
    private int|float \$number;
 
    public function setNumber(int|float \$number): void {
        \$this->number = \$number;
    }
 
    public function getNumber(): int|float {
        return \$this->number;
    }
}

Nota che void non può mai essere parte di uno union type, poiché indica "nessun valore di ritorno". Inoltre, le unioni nullable possono essere scritte utilizzando |null o utilizzando l'esistente ? notazione:

public function bar(?Bar \$bar): void;

Link: wiki.php.net/rfc/union_types_v2

JIT

Il compilatore JIT - just in time - promette miglioramenti significativi delle prestazioni, anche se sembrano ancora marginali nel contesto delle richieste web. Ma, come si legge dalla RFC, questo potrebbe aprire le porte a PHP in ambienti dove fino ad oggi non veniva preso in considerazione.

Link: wiki.php.net/rfc/jit

L'operatore nullsafe (?->)

Se hai familiarità con l'operatore di coalescenza null, conosci già i suoi difetti: non funziona sulle chiamate di metodo. Sono invece necessari controlli intermedi o affidarsi ad helper opzionali forniti da alcuni framework:

\$country =  null;
 
if (\$session !== null) {
    \$user = \$session->user;
 
    if (\$user !== null) {
        \$address = \$user->getAddress();
 
        if (\$address !== null) {
            \$country = \$address->country;
        }
    }
}
 
// do something with \$country

Con l'aggiunta dell'operatore nullsafe, ora possiamo avere un comportamento simile a coalescenza nulla sui metodi:

\$country = \$session?->user?->getAddress()?->country;

// do something with \$country

Link: wiki.php.net/rfc/nullsafe_operator

Argomenti denominati

Gli argomenti denominati ti consentono di passare valori a una funzione, specificando il nome del valore, in modo da non dover prendere in considerazione il loro ordine e puoi anche saltare i parametri opzionali!

array_fill(0, 100, 50);

array_fill(start_index: 0, num: 100, value: 50);

array_fill(value: 50, num: 100);

Link: wiki.php.net/rfc/named_params

Attributi

Gli attributi, comunemente noti come annotazioni in Java e come decoratori in Python, offrono un modo per aggiungere metadati alle classi, senza dover analizzare i docblock.

Ecco un esempio di come appaiono gli attributi:

<<ExampleAttribute>>
class Foo
{
    <<ExampleAttribute>>
    public const FOO = 'foo';
 
    <<ExampleAttribute>>
    public \$x;
 
    <<ExampleAttribute>>
    public function foo(<<ExampleAttribute>> \$bar) { }
}
 
\$object = new <<ExampleAttribute>> class () { };
 
<<ExampleAttribute>>
function f1() { }
 
\$f2 = <<ExampleAttribute>> function () { };
 
\$f3 = <<ExampleAttribute>> fn () => 1;

Link: wiki.php.net/rfc/attributes_v2

Espressione Match

Potresti chiamarlo il fratello maggiore dell'espressione switch: match può restituire valori, non richiede istruzioni break, può combinare condizioni, usa confronti di tipo rigorosi e non esegue alcuna coercizione di tipo.

Assomiglia a questo, prima:

switch (\$this->lexer->lookahead['type']) {
    case Lexer::T_SELECT:
        \$statement = \$this->SelectStatement();
        break;
 
    case Lexer::T_UPDATE:
        \$statement = \$this->UpdateStatement();
        break;
 
    case Lexer::T_DELETE:
        \$statement = \$this->DeleteStatement();
        break;
 
    default:
        \$this->syntaxError('SELECT, UPDATE or DELETE');
        break;
}

Dopo:

\$statement = match (\$this->lexer->lookahead['type']) {
    Lexer::T_SELECT => \$this->SelectStatement(),
    Lexer::T_UPDATE => \$this->UpdateStatement(),
    Lexer::T_DELETE => \$this->DeleteStatement(),
    default => \$this->syntaxError('SELECT, UPDATE or DELETE'),
};

Link: wiki.php.net/rfc/match_expression_v2

Constructor Property Promotion

Questa RFC aggiunge una maniera molto più concisa per dichiarare e assegnare parametri in fase di dichiarazione del costruttore. Invece di fare questo:

class Point {
    public float \$x;
    public float \$y;
    public float \$z;
 
    public function __construct(
        float \$x = 0.0,
        float \$y = 0.0,
        float \$z = 0.0,
    ) {
        \$this->x = \$x;
        \$this->y = \$y;
        \$this->z = \$z;
    }
}

Puoi fare questo:

class Point {
    public function __construct(
        public float \$x = 0.0,
        public float \$y = 0.0,
        public float \$z = 0.0,
    ) {}
}

Link: wiki.php.net/rfc/constructor_promotion

Nuovo tipo di ritorno static

Sebbene fosse già possibile restituire self e parent, static non era un tipo valido da restitire fino a PHP 8. Data la natura dinamicamente tipizzata di PHP, è una funzionalità che sarà utile a molti sviluppatori.

class Test {
    public function createFromWhatever(\$whatever): static {
        return new static(\$whatever);
    }
}

Link: wiki.php.net/rfc/static_return_type

Nuovo tipo mixed

Il tipo mixed ha provocato nella comunità sentimenti contrastanti ma potremmo definirlo un male necessario: in effetti un tipo mancante può comportare molte cose in PHP:

  • Una funzione non restituisce nulla o null
  • Ci aspettiamo un risultato di diversi tipi
  • Ci aspettiamo un tipo che non può essere suggerito da PHP

Per i motivi di cui sopra, è un bene che sia stato aggiunto il tipo mixed. Che significa, uno di questi tipi:

  • array
  • bool
  • callable
  • int
  • float
  • null
  • object
  • resource
  • string

Da notare mixed può essere utilizzato anche come parametro o tipo di proprietà, non solo come tipo restituito.

Inoltre, poiché mixed include già null, non è consentito renderlo nullable. Quanto segue attiverà un errore:

//INVALID - Fatal error: Mixed types cannot be nullable, null is already part of the mixed type.
function bar(): ?mixed {}

Link: wiki.php.net/rfc/mixed_type_v2

L'espressione throw

Questa RFC cambia throw dall'essere un'istruzione all'essere un'espressione, il che rende possibile lanciare un'eccezione in molti posti dove prima era impossibile:

// This was previously not possible since arrow functions only accept a single expression while throw was a statement.
\$callable = fn() => throw new Exception();
 
// \$value is non-nullable.
\$value = \$nullableValue ?? throw new InvalidArgumentException();
 
// \$value is truthy.
\$value = \$falsableValue ?: throw new InvalidArgumentException();
 
// \$value is only set if the array is not empty.
\$value = !empty(\$array)
    ? reset(\$array)
    : throw new InvalidArgumentException();

Link: wiki.php.net/rfc/throw_expression

Ereditarietà con metodi privati

In precedenza, PHP applicava gli stessi controlli di ereditarietà su metodi pubblici, protetti e privati. In altre parole: i metodi privati dovrebbero seguire le stesse regole dei metodi protetti e pubblici. Questo non ha senso, poiché i metodi privati non saranno accessibili dalle classi figlie.

Questa RFC ha modificato tale comportamento, in modo che questi controlli di ereditarietà non vengano più eseguiti sui metodi privati. Inoltre, anche l'uso della funzione final private non aveva senso, quindi così facendo ora si attiverà un warning:

// Private methods cannot be final as they are never overridden by other classes

Link: wiki.php.net/rfc/inheritance_private_methods

WeakMap

Le WeakMap consentono di creare una mappa di oggetti a valori arbitrari senza impedire che gli oggetti utilizzati come chiavi vengano spostati nel garbage collector. Se la chiave di un oggetto va nel garbage collector, verrà semplicemente rimossa dalla mappa.

Prendiamo l'esempio degli ORM, che spesso implementano cache che contengono riferimenti a classi per migliorare le prestazioni delle relazioni tra entità. Questi oggetti non possono essere raccolti dal garbage collector, finché questa cache abbia un riferimento ad essi, anche se la cache è l'unica cosa che fa riferimento ad essi.

Se invece per la cache utilizziamo weak reference e weak map, PHP sposterà nel garbage collector questi oggetti quando nient'altro farà più riferimento ad essi. Soprattutto nel caso degli ORM, che possono gestire diverse centinaia, se non migliaia di entità all'interno di una richiesta; le weak map possono offrire un modo migliore e più performante di trattare questi oggetti.

Ecco come appaiono le mappe deboli:

class Foo 
{
    private WeakMap \$cache;
 
    public function getSomethingWithCaching(object \$obj): object
    {
        return \$this->cache[\$obj]
           ??= \$this->computeSomethingExpensive(\$obj);
    }
}

Link: wiki.php.net/rfc/weak_maps

Consentire ::class sugli oggetti

Una piccola, ma utile, nuova funzionalità: ora è possibile usare ::class sugli oggetti, invece di dover usare get_class() su di essi. Ritorna sempre il nome della classe come stringa.

\$foo = new Foo();
var_dump(get_class(\$foo)); // PHP < 8
var_dump(\$foo::class); // PHP >= 8

Link: wiki.php.net/rfc/class_name_literal_on_object

Variabile di cattura opzionale nei catch

Ogni volta che volevi catturare un'eccezione prima di PHP 8, dovevi memorizzarla in una variabile, indipendentemente dal fatto che tu usassi quella variabile o meno. Con la non-capturing catches, puoi omettere la variabile. Invece di questo:

try {
    changeImportantData();
} catch (PermissionException \$ex) {
    echo "You don't have permission to do this";
}

Ora puoi farlo:

try {
    changeImportantData();
} catch (PermissionException) { 
    echo "You don't have permission to do this";
}

Nota che è necessario specificare sempre il tipo, non ti è permesso avere un catch vuoto.

Link: wiki.php.net/rfc/non-capturing_catches

Virgola finale negli elenchi di parametri

Già possibile quando si chiama una funzione, il supporto delle virgole finali mancava ancora negli elenchi di parametri: in PHP 8 è consentito. Il che significa che puoi fare quanto segue:

public function(
    string \$parameterA,
    int \$parameterB,
    Foo \$objectfoo,
) {
    // …
}

Link: wiki.php.net/rfc/trailing_comma_in_parameter_list

Nuova interfaccia Stringable

L'interfaccia Stringable può essere utilizzata come suggerimento di tipo per qualsiasi cosa implementi __toString(). Ogni volta che una classe implementa __toString(), implementa automaticamente l'interfaccia dietro le quinte e non è necessario implementarla manualmente.

class Foo
{
    public function __toString(): string
    {
        return 'foo';
    }
}

function bar(string|Stringable \$stringable) { /* … */ }

bar(new Foo());
bar('abc');

Link: wiki.php.net/rfc/stringable

Nuova funzione str_contains()

Finalmente non dobbiamo più fare affidamento su strpos() per sapere se una stringa contiene un'altra stringa.

Invece di fare questo:

if (strpos('cerca ago nel pagliaio', ‘ago’) !== false) { /* … */ }

Adesso puoi fare

if (str_contains('cerca ago nel pagliaio', ‘ago’)) { /* … */ }

Link: wiki.php.net/rfc/str_contains

Nuove funzioni str_starts_with() e str_ends_with()

Altre due funzioni attese da tempo, sono ora aggiunte nel core.

str_starts_with('haystack', 'hay'); // true
str_ends_with('haystack', 'stack'); // true

Link: wiki.php.net/rfc/add_str_starts_with_and_ends_with_functions

Nuova funzione get_debug_type()

get_debug_type() restituisce il tipo di una variabile. Qualcosa come gettype() ma con un output più utile per array, stringhe, classi anonime e oggetti.

Ad esempio, chiamando gettype() su una classe \Foo\Bar restituirebbe un oggetto. L'uso di get_debug_type() restituirà il nome della classe.

Link: wiki.php.net/rfc/get_debug_type

Cambiamenti decisivi

Come accennato prima: si tratta di un aggiornamento importante e quindi ci saranno modifiche sostanziali. La cosa migliore da fare è dare un'occhiata all'elenco completo delle modifiche più importanti nel documento di upgrade.

Molte di queste modifiche sono state deprecate nelle precedenti versioni 7.* Quindi, se ti sei aggiornato nel corso degli anni, non dovresti avere difficoltà a passare a PHP 8.

Errori di tipo coerenti

Le funzioni definite dall'utente in PHP lanciano già un TypeError, ma le funzioni interne no, lanciano un warning e restituiscono null. A partire da PHP 8 il comportamento delle funzioni interne è stato reso coerente.

Avvisi motore riclassificati

Molti errori che in precedenza attivavano solo avvisi o notifiche, sono stati convertiti in errori appropriati. I seguenti warning sono stati modificati.

  • Variabile non definita: eccezione Error anziché notice
  • Indice array non definito: warning invece di notice
  • Divisione per zero: eccezione DivisionByZeroError invece di warning
  • Tentativo di aumentare/diminuire la proprietà '%s' di un non-oggetto: eccezione Error invece di warning
  • Tentativo di modificare la proprietà '%s' di un non-oggetto: eccezione Error invece di warning
  • Tentativo di assegnare la proprietà '%s' di un non-oggetto: eccezione Error invece di warning
  • Creazione di un oggetto da un valore vuoto: eccezione Error invece di warning
  • Tentativo di ottenere la proprietà '%s' di non-oggetto: warning invece di notice
  • Proprietà non definita: %s:$%s: warning invece di notice
  • Impossibile aggiungere un elemento all'array poiché l'elemento successivo è già occupato: eccezione Error invece di warning
  • Impossibile annullare l'offset in una variabile non-array: eccezione Error invece di warning
  • Impossibile utilizzare un valore scalare come un array: eccezione Error invece di warning
  • È possibile decomprimere solo array e Traversable: eccezione TypeError invece di warning
  • Argomento non valido fornito per foreach(): eccezione TypeError invece di warning
  • Tipo di offset non valido: eccezione TypeError invece di warning
  • Tipo di offset non valido in isset o vuoto: eccezione TypeError invece di warning
  • Tipo di offset non valido in unset: eccezione TypeError invece di warning
  • Conversione da array a stringa: warning invece di notice
  • ResourceID#%d utilizzato come offset, casting a numero intero (%d): warning invece di notice
  • Offset stringa non inizializzata: %d: warning invece di notice
  • Impossibile assegnare una stringa vuota a un offset di stringa: eccezione Error invece di warning
  • La risorsa fornita non è una risorsa di stream valida: eccezione TypeError invece di warning

Link: wiki.php.net/rfc/engine_warnings

L'operatore @ non silenzia più gli errori fatali

È possibile che questa modifica possa rivelare errori che erano ancora nascosti prima di PHP 8. Assicurati di impostare display_errors = Off sui tuoi server di produzione! Ma soprattutto, risolvi gli errori… non nasconderli sotto al tappeto!

Livello di segnalazione errori predefinito

Ora è E_ALL invece di E_ALL tranne E_NOTICE e E_DEPRECATED. Ciò significa che potrebbero apparire molti errori che in precedenza erano stati ignorati silenziosamente, sebbene probabilmente esistessero già prima di PHP 8.

Modalità di errore PDO predefinita

Da RFC: la modalità di errore predefinita corrente per PDO è silenziosa. Ciò significa che quando si verifica un errore SQL, non possono essere emessi errori o avvisi e non possono essere generate eccezioni a meno che lo sviluppatore non implementi la propria gestione esplicita degli errori.

Questa RFC modifica l'errore predefinito in PDO::ERRMODE_EXCEPTION in PHP 8.

Link: wiki.php.net/rfc/pdo_default_errmode

Precedenza di concatenazione

Sebbene fosse già deprecato in PHP 7.4, questa modifica è ora applicata. Se scrivessi qualcosa di simile:

echo "somma:" . \$a + \$b;

PHP lo avrebbe interpretato in precedenza in questo modo:

echo ("somma:" . \$a) + \$b;

PHP 8 farà in modo che sia interpretato in questo modo:

echo "somma:". (\$a + \$b);

Link: wiki.php.net/rfc/concatenation_precedence

Per maggiori informazioni, potete consultare la guida di upgrade di PHP.

Ultima modifica: venerdì 23 ottobre 2020

Ancora nessun commento presente

Che ne dici di essere il primo?

Aggiungi il tuo commento

Iscriviti alla mia newsletter

Resterai informato sugli ultimi post, appena verranno pubblicati