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);