PHP 8 verrà ufficialmente rilasciato il 26 novembre 2020. Dato che si tratta di una major release avremo da un lato nuove funzionalità, dall’altro ci saranno dei cambiamenti che potrebbero rendere incompatibile il nostro vecchio codice. Ci sono, come detto, tante nuove funzionalità e soprattutto dei miglioramenti della velocità, che vedremo in seguito.
Attualmente lo sviluppo è molto vivo e la prima versione Alpha è già uscita il 25 giugno di quest’anno.
Date le novità e i cambiamenti introdotti, è possibile che dovremo modificare il nostro codice in alcuni punti, per essere compatibile appunto con PHP 8. Ma se ci siamo tenuti aggiornati alle ultime release non dovemmo sudare molto, visto che molte delle funzionalità abbandonate (deprecated) erano già tali dalla 7. Vedremo le deprecated in questo articolo.
Oltre tutto, come accennato, PHP 8 introdurrà un nuovo set di feature, incluso il nuovo compilatore JIT, l’unione dei tipi, attributi e altro.
Nuove funzionalità
Partiamo con le novità, tenendo presente che PHP 8 è attualmente in sviluppo e questa lista crescerà nel tempo.
Unioni di tipi (Union types)
Data la natura dinamica della tipizzazione dei dati in PHP, le union types potranno essere sicuramente utili in tanti casi. Sono l’unione di due o più tipi che indicano che almeno uno di essi può essere usato:
public function prova(Foo|Bar $input): int|float;
Da notare che il tipo void
non può essere parte di una union, dato che significa “nessun valore”. Invece, le unioni nullable
possono continuare ad esistere, sempre anteponendo il ?
al tipo:
public function prova(Foo|null $foo): void;
public function prova2(?Bar $bar): void;
JIT compiler
Una delle novità più attese è il nuovo compilatore JIT – just in time – che promette un aumento delle prestazioni significativo, anche se non sempre nel contesto delle richieste web. Gli attuali benchmark su JIT in PHP 8 ci dicono che il guadagno di velocità è altalenante, attendiamo la versione finale per dare un giudizio complessivo.
Per approfondire questo argomento, leggete questo articolo dedicato su PHP 8 e JIT.
Annotations v2
Gli Attributes, chiamati in altri linguaggi annotations, offrono un modo veloce per aggiungere metadati alle classi, senza doversi occupare di fare il parse dei cosiddetti docblocks. Concetti molto simili esistono già in Java, C#, C++ e assomigliano ai Decorators di Python e Javascript.
Questo tipo di notazione non è mai stata ufficiale, e sarà molto familiare a chi utilizza le annotations in Doctrine. Essendo un argomento molto vasto vi rimando direttamente alla rfc
Diamo ora uno sguardo rapido alle nuove annotations:
@@ExampleAttribute
class Foo
{
@@ExampleAttribute
public const FOO = 'foo';
@@ExampleAttribute
public $x;
@@ExampleAttribute
public function foo(@@ExampleAttribute $bar) { }
}
Espressione Match
Il nuovo costrutto match è considerabile come il fratello maggiore di switch : match infatti può restituire i valori e non richiede l’uso del break. Si possono inoltre combinare le condizioni e usare la tipizzazione nei confronti:
$prova = match($input) { 0 => "ciao", '1', '2', '3' => "mondo", };
Per approfondire l’utilizzo di match , date uno sguardo all’articolo su match vs switch in PHP 8!
Creazione di proprietà nel costruttore migliorata
Questa nuova RFC aggiungi una sintassi molto interessante: potremo infatti sfruttare un meccanismo per indicare esplicitamente la visibilità di una proprietà creata dinamicamente dal costruttore di una classe. Invece di fare questo:
class Contatto { public Persona $persona; public int $eta; public function __construct( Persona $persona, int $eta, ) { $this->persona = $persona; $this->eta = $eta; } }
Potremo fare questo:
class Contatto { public function __construct( public Persona $persona, public int $eta, ) {} }
Questo è solo uno sguardo generale, per approfondire c’è l’articolo dedicato.
Nuovo tipo static
Anche se era già possibile fare il return di tipo self , static viene aggiunto con la versione 8 di PHP. Data la tipizzazione debole di PHP, potrebbe essere utile in varie situazioni.
class Foo { public function test(): static { return new static(); } }
Nuovo tipo mixed
Anche se lo abbiamo letto mille volte nel manuale ufficiale, adesso potremo utilizzarlo nel nostro codice. Sì, potrebbe non essere necessariamente una cosa positiva, ma tant’è. Principalmente potremmo utilizzarlo al posto di null , o più praticamente se ci aspettiamo che un metodo restituisca più tipi di valore (..ma sarebbe meglio di no) o in generale quando non possiamo fare un type-hinting convincente.
Tanto per ricordalo, mixed può essere uno di questi tipi:
- array
- bool
- callable
- int
- float
- null
- object
- resource
- string
Ovviamente potremo utilizzare mixed anche come tipo di proprietà o parametro. Prestiamo però attenzione, perché mixed include null e non è possibile renderlo “nullable”. Quindi questo tipo di sintassi ci porterà ad errore:
// Fatal error: Mixed types cannot be nullable, null is already part of the mixed type. function prova(): ?mixed {}
Throw è un espressione
throw non è più classificato come statement, ma come expression, quindi potremo utilizzarlo in più contesti, come ad esempio:
$foo = $bar['offset'] ?? throw new OffsetDoesNotExist('offset');
Ereditarietà di metodi private
In precedenza, PHP applicava il controllo di visibilità sui metodi con visibilità public , protected e private quando li ereditavamo. Questo faceva sì che i metodi private seguissero le stesse regole dei protected e public . Non aveva molto senso, in quanto i metodi private non sono accessibili dalle classi che ereditano.
In PHP questa regola cambia, e i controlli sui metodi ereditati con visibilità private non vengono eseguiti più. Inoltre, l’uso di final private function non ha molto senso, e adesso genera un warning:
Warning: Private methods cannot be final as they are never overridden by other classes
::class sugli oggetti
Una piccola ma utilissima modifica, permette adesso di utilizzare il ::class direttamente sugli oggetti, senza dover utilizzare get_class(). Funziona allo stesso modo:
$foo = new Foo(); var_dump($foo::class);
Eccezioni senza variabile
Nel fare il catch di una Exception prima di PHP 8, dovevamo indicare una variabile nella quale mettere questa Exception , indipendentemente dal fatto che la usassimo o no. Con i non-capturing catch potremo ometterla. Invece di scrivere questo:
try { // ... } catch (Exception $e) { Log::error("Errore"); }
Potremo fare questo:
try { // ... } catch (Exception) { Log::error("Errore"); }
Ovviamente dovremo continuare a specificare un tipo di Exception , non potremo certo lasciare il catch vuoto. Se volessimo fare il catch di tutte le Exception e gli errori, dovremmo indicare Throwable come tipo.
Ultima virgola nella lista parametri
Anche se già implementata nelle funzioni, adesso sarà possibile lasciare l’ultima virgola nella lista parametri di un metodo:
public function( string $parameterA, int $parameterB, Foo $objectfoo, // Ultima virgola! ) { // … }
Nuova interface Stringable
Con PHP 8 viene introdotta una nuova interfaccia, Stringable , che può essere utilizzata come tipo su qualsiasi stringa o su una classe che implementi il metodo __toString() . Ancora meglio, qualsiasi classe implementi __toString() implementerà automaticamente questa interfaccia, anche se non lo dichiariamo implicitamente.
class Foo { public function __toString(): string { return 'foo'; } } function bar(Stringable $stringable) { /* … */ } bar(new Foo()); bar('abc');
Nuova funzione str_contains()
Ce l’abbiamo fatta. Non dovremo più utilizzare strpos() per sapere se una stringa ne contiene un’altra. Anziché utilizzare questo:
if (strpos('Ciao, come stai?', 'come') !== false) { /* … */ }
Potremo semplicemente fare:
if (str_contains('Ciao, come stai?', 'come')) { /* … */ }
Nuove funzioni str_starts_with() e str_ends_with()
Quando pensavamo che le sorprese fossero finite, ecco che PHP 8 introduce queste due nuove funzioni per evitare di pasticciare con substr() e compagnia bella, semplicemente scrivendo:
str_starts_with('haystack', 'hay'); // true str_ends_with('haystack', 'stack'); // true
Nuova funzione fdiv()
La nuova function fdiv() si comporta in modo simile a fmod() e intdiv() , che permettono la divisione by zero. Invece dell’errore riceveremo INF , -INF o NAN a seconda dei casi.
Nuova funzione get_resource_id()
Le variabili che puntano a una risorsa sono un caso speciale in PHP, perché fanno riferimento appunto a risorse esterne e non sono direttamente interpretabili. L’esempio più facile (e sicuramente diffuso) è quello di una connessione MySQL, o del puntatore di una risorsa file.
Ognuna di queste risorse è riconducibile ad un ID: in precedenza l’unico modo per leggere questo ID era fare il cast al tipo int:
$id_risorsa = (int) $resource;
PHP 8 aggiunge la funzione get_resource_id() , rendendo questa operazione type-safe:
$id_risorsa = get_resource_id($resource);
Metodi astratti nei trait migliorati
I trait in PHP possono dichiarare dei metodi che possono essere implementati dalle classi che li utilizzano, ma c’è una limitazione: prima di PHP 8, infatti, essi subivano una validazione abbastanza permissiva, soprattutto riguardo al return type nell’implementazione da parte della classe del metodo astratto in questione. Questo codice, in PHP 7+ è infatti valido:
trait Test { abstract public function test(int $input): int; } class UsesTrait { use Test; public function test($input) { return $input; } }
PHP 8 controllerà invece il tipo dei metodi implementati. Dovremo scrivere questo:
class UsesTrait { use Test; public function test(int $input): int { return $input; } }
Breaking change
Come vi dicevo: questa è una major release e pertanto ci saranno dei cambiamenti distruttivi (tradurlo fa sempre un certo effetto). Questo vuole dire che probabilmente dovremo lavorare un po’ sul nostro codice per renderlo compatibile con PHP 8, ma la verità è che molte delle modifiche che andremo a vedere erano già deprecate in PHP 7, quindi se avevamo fatto una revisione del codice ai tempi, dovremmo essere a posto. In ogni caso diamo uno sguardo a quali cambiamenti ci saranno:
I parametri di tipo diverso generano una Exception di tipo TypeError
Passando un parametro del tipo non corretto ad una funzione scritta dall’utente, viene generata una Exception TypeError . Per le funzioni interne, quelle predefinite di PHP, invece, questo dipende, alcune restituiscono null , altre un warning. A partire da PHP 8, verrà sempre lanciata l’Exception TypeError (così come ArgumentCountError , che è una sua discendente). Nell’esempio vediamo come, attualmente, ci sia un’inconsistenza in quanto strlen() restituisce null :
function foo(int $bar) {} foo("not an int"); // TypeError: Argument 1 passed to foo() must be of the type int, string given var_dump(strlen(new stdClass)); // Warning: strlen() expects parameter 1 to be string, object given // NULL
Da PHP 8, lo stesso genererà una TypeError :
var_dump(strlen(new stdClass)); // TypeError: strlen() expects parameter 1 to be string, object given
Nuova gestione degli errori
Ricollegandoci all’argomento precedente, molte funzioni che generano dei Warning o delle Notice, adesso avranno un comportamento più consistente:
- Undefined variable: Exception Error anziché una Notice
- Undefined array index: Warning anziché Notice
- Division by zero: invece di un Warning verrà lanciata l’Exception DivisionByZeroError
- Attempt to increment/decrement property of non-object: Exception Error invece di un Warning
- Attempt to modify property of non-object: Exception Error invece di un Warning
- Attempt to assign property of non-object: Exception Error invece di un Warning
- Creating default object from empty value: Exception Error invece di un Warning
- Trying to get property of non-object: avremo un Warning invece di una Notice
- Undefined property: avremo un Warning invece di una Notice
- Cannot add element to the array as the next element is already occupied: Exception Error invece di un Warning
- Cannot unset offset in a non-array variable: Exception Error invece di un Warning
- Cannot use a scalar value as an array: Exception Error invece di un Warning
- Invalid argument supplied for foreach(): Exception TypeError invece di un Warning
- Illegal offset type: Exception TypeError invece di un Warning
- Illegal offset type in isset or empty: Exception TypeError invece di un Warning
- Illegal offset type in unset: Exception TypeError invece di un Warning
- Supplied resource is not a valid stream resource: Exception TypeError invece di un Warning
- Cannot assign an empty string to a string offset: Exception Error invece di un Warning
L’operatore @ non silenzia i fatal error
Facciamo attenzione, questo cambiamento potrebbe far venire fuori degli errori che in precedenza avevamo silenziato utilizzando l’operatore @ prima di PHP 8. Accertiamoci che sia impostato display_errors=off in produzione.
Nuovo livello di default di error reporting
Il nuovo livello di default di Error reporting è E_ALL , quindi, come dicevamo nel paragrafo precedente, assicuriamoci che non saltino fuori errori in produzione.
PDO utilizzerà di default PDOException
PDO di default genera errori silenti. Questo significa che, ad esempio, quando avviene un errore SQL, non viene generato alcun errore o Warning, a meno che non avessimo implementato noi stessi un controllo dell’errore esplicito. Da PHP 8, PDO farà il throw di una PDOException quando uno di questi errori accade.
Precedenza nella concatenazione
Anche se già deprecato in PHP 7.4, questo cambia avverrà definitivamente con la release di PHP 8. In precedenza, se avessimo scritto questo:
echo "somma: " . $a + $b;
PHP lo interpreterà così:
echo ("somma: " . $a) + $b;
Da PHP 8 verrà invece interpretato così:
echo "somma: " . ($a + $b);