Le principali funzionalità di PHP 8.0

Una panoramica sulle modifiche sostanziali, le nuove funzionalità e miglioramenti di prestazioni di PHP 8.0

Maico Orazio
weBeetle
8 min readMay 26, 2023

--

PHP 8 è stato rilasciato il 26 novembre 2020. In questo articolo ho provato ad illustrare le modifiche sostanziali, oltre a schematizzare le molte nuove funzionalità e miglioramenti di prestazioni.

Just-n-Time (JIT)

RFC https://wiki.php.net/rfc/jit

Una delle funzionalità più interessanti è il compilatore Just In Time (JIT), che promette significativi miglioramenti delle prestazioni, anche se non sempre nel contesto delle richieste web.

PHP è un linguaggio interpretato, non compilato come un programma scritto in C o Java, questo significa che viene tradotto in codice macchina in fase di esecuzione. JIT è una tecnica che compila parti del codice in fase di esecuzione, in modo che la versione compilata possa essere utilizzata al suo posto.

Capiamo come funziona effettivamente JIT e come potrebbe migliorare le prestazioni delle nostre applicazioni.
L’esecuzione di PHP consiste in una procedura di 4 fasi:

  1. L’interprete legge il codice PHP e costruisce un set di token
  2. Analizza lo script php, esaminando le parti tokenizzate: il motore php crea un albero astratto della sintassi, una rappresentazione gerarchica della struttura del codice sorgente
  3. Lo script viene compilato: l’interprete attraversa l’albero e traduce i nodi in Zend OPcodes di basso livello, identificatori numerici di singole operazioni che possono essere eseguite dalla Zend Virtual Machine (Zend VM)
  4. Viene avviato il processo di esecuzione: gli OPCodes vengono interpretati ed eseguiti sulla Zend VM

JIT fondamentalmente traduce le parti calde del codice intermedio in codice macchina e usa OPcache per memorizzarle e non compilarle di nuovo successivamente, se non sono state modificate. Quando il motore sta per processare lo script php, JIT controlla prima se esiste già una versione compilata di alcune delle sue parti nella cache. Se non sono presenti, il processo segue i passi precedentemente riportati e, prima di eseguirlo (passo 4), viene salvato nella cache operativa. Se sono già presenti nella cache, allora semplicemente le estrae e le esegue.

Tuttavia, poiché PHP è utilizzato soprattutto in un contesto web, dovremmo misurare in questo contesto l’impatto del JIT. Si scopre che, sfortunatamente, c’è molto meno codice da memorizzare in cache durante la gestione di una richiesta web.

Contributo JIT relativo alle prestazioni di PHP 8

Anche se il JIT potrebbe non offrire miglioramenti significativi a breve termine, aprirà molte possibilità per la crescita di PHP, sia come linguaggio web che come linguaggio più generico.

Argomenti denominati

RFC https://wiki.php.net/rfc/named_params

Esistono in PHP funzioni che da sempre hanno creato confusione sull’ordine degli argomenti accettati. Ecco alcuni esempi:

$numbers = [1, 2, 3, 4, 5, 6];
$evens = array_filter($numbers, function($n){
return $n % 2 === 0;
});
$squares = array_map(function($n) {
return $n * $n;
}, $numbers);

var_dump($evens, $squares);
array(3) {
[1]=>
int(2)
[3]=>
int(4)
[5]=>
int(6)
}
array(6) {
[0]=>
int(1)
[1]=>
int(4)
[2]=>
int(9)
[3]=>
int(16)
[4]=>
int(25)
[5]=>
int(36)
}

In PHP 8.0 abbiamo la possibilità di specificare il nome dell’argomento da passare alla funzione, non considerando l’ordine come riportato nella dichiarazione della funzione:

$numbers = [1, 2, 3, 4, 5, 6];
$evens = array_filter(array: $numbers, callback: function($n){
return $n % 2 === 0;
});
$squares = array_map(array: $numbers, callback: function($n){
return $n * $n;
});

var_dump($evens, $squares);

Possiamo saltare i parametri con i valori predefiniti:

/*
firma del metodo
setcookie (
string $name,
string $value = “”,
int $expires = 0,
string $path = “”,
string $domain = “”,
bool $secure = false,
bool $httponly = false,
) : bool
*/

setcookie(
name: 'test',
expires: time() + 60 * 60 * 2,
);

E’ possibile utilizzare anche la decompressione degli array:

class User
{
public function __construct(
public string $name,
public string $sex,
public int $age,
) {}
}
$input = [
'age' => 39,
'name' => 'Maico',
'sex' => 'Male',
];
$user = new User(…$input);
var_dump($user); // object(User) {'name'=> 'Maico', 'sex'=>'Male', 'age'=>39}

Unione di Tipi

RFC https://wiki.php.net/rfc/union_types_v2

In PHP 7.4 è stata introdotta la possibilità di specificare il tipo di una proprietà di classe; se, come nell’esempio sotto, abbiamo bisogno di due tipi per la stessa proprietà, possiamo specificarlo tramite l’annotazione:

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

In PHP 8.0 abbiamo la possibilità di specificarlo ulteriormente usando la notazione pipe |.

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

Questo funziona non solo sui tipi primitivi, ma anche sulle classi, come DateTime o Exception.

Possiamo utilizzare l’unione dei tipi ovunque poiché è possibile impostare, recuperare o dichiarare una proprietà della classe.

Constructor Property Promotion

RFC https://wiki.php.net/rfc/constructor_promotion

In PHP 7.4 è necessario dichiarare le proprietà di una classe che poi possono essere valorizzate dal costruttore:

class User 
{
public string $firstname;
public string $lastname;
public int $age;

public function __construct(
string $firstname,
string $lastname,
int $age
)
{
$this->firstname = $firstname;
$this->lastname = $lastname;
$this->age = $age;
}
}

PHP 8.0 aggiunge zucchero sintattico e ci consente di utilizzare la nuova sintassi usando la Constructor Property Promotion nel modo seguente:

class User 
{
public function __construct(
public string $firstname,
public string $lastname,
public int $age
)
{}
}

In breve, consente di combinare la definizione delle proprietà, la definizione del costruttore e le assegnazioni delle proprietà in un’unica sintassi, nell’elenco dei parametri del costrutto.

La Constructor Property Promotion dunque è una caratteristica utilissima soprattutto quando abbiamo molti argomenti che valorizzeranno le relative proprietà della classe.

throw come espressione

RFC https://wiki.php.net/rfc/throw_expression

In PHP 8.0, throw è stata convertita in un’espressione in modo da essere utilizzata in qualsiasi contesto in cui sono consentite le espressioni. Ad esempio, arrow function, operatore di coalescenza nullo, operatori ternari ecc.

$callable = fn() => throw new Exception();
// $value is non-nullable.
$value = $nullableValue ?? throw new InvalidArgumentException();
// $value is truthy.
$value = $falsableValue ?: throw new InvalidArgumentException();

Operatore Nullsafe

RFC https://wiki.php.net/rfc/nullsafe_operator

In PHP 8.0 è stato introdotto l’operatore speciale nullsafe ?, che ha un comportamento simile all’operatore di coalescenza nullo ma applicato ai metodi; sostanzialmente controlla se il dato esiste e non è nullo, senza generare alcun errore.

$country = null;
// PHP 7.4
if ($session !== null) {
$user = $session->getUser();
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$country = $address->country;
}
}
}
// PHP 8.0
$country = $session?->getUser()?->getAddress()?->country;

Esistono alcune differenze con l’operatore di coalescenza nullo. E’ possibile utilizzare l’operatore di coalescenza nullo in combinazione con le chiavi dell’array, mentre l’operatore nullsafe non può gestirle:

$array = [];
var_dump($array['key']->foo ?? null);
var_dump($array['key']?->foo); // Warning: Undefined array key "key"

L’operatore nullsafe, d’altra parte, può funzionare con chiamate di metodo, mentre l’operatore di coalescenza nullo no:

class Invoice
{
public function getDate(): ?DateTime { /* … */ }
// …
}
$invoice = new Invoice();
var_dump($invoice->getDate()?->format('Y-m-d')); // null

È possibile utilizzare l’operatore nullsafe per invocare il metodo format() sull’oggetto DateTime, anche quando è null, mentre l’operatore di coalescenza nullo andrebbe in crash:

var_dump($invoice->getDate()->format('Y-m-d') ?? null);
// Fatal error: Uncaught Error: Call to a member function format() on null

Espressione di corrispondenza

RFC https://wiki.php.net/rfc/match_expression_v2

E’ equivalente allo switch ma in una versione più semplice.

$statusCode = 300;
// PHP 7.4
switch ($statusCode) {
case 200:
case 300:
$message = null;
break;
case 400:
$message = 'bad request';
break;
case 500:
$message = 'server error';
break;
default:
$message = 'unknown status code'
break;
}

// PHP 8.0
$message = match ($statusCode) {
200, 300 => null,
400 => 'bad request',
500 => 'server error',
default => 'unknown status code'
}

L’espressione match:

  • Non richiede istruzioni break
  • Può combinare diverse condizioni in una usando come separatore la virgola
  • Restituisce un valore, quindi è necessario assegnare il valore solo una volta
  • Esegue controlli rigorosi sul tipo, usa === invece di ==

Attributi

RFC https://wiki.php.net/rfc/attributes_v2

Ora parliamo di uno dei più grandi cambiamenti in PHP 8.0, ovvero gli attributi.

Comunemente noti come annotazioni in molti altri linguaggi, gli attributi in PHP offrono un modo per aggiungere metadati alle classi, ai metodi, alle variabili e altro, in modo strutturato, senza dover analizzare il DocBlock.

E’ possibile definire degli attributi personalizzati, semplici classi, annotate a loro volta con l’attributo.

#[Attribute]
class ListensTo
{
public string $event;
public function __construct(string $event)
{
$this->event = $event;
}
}

Come evidenziato in precedenza, gli attributi possono essere aggiunti in diversi punti: classi, metodi e funzioni, proprietà e costanti, funzioni anonime, argomenti di metodi e argomenti di funzioni. Possono essere dichiarati prima o dopo i DocBlock.

Possono prendere uno o più argomenti, definiti dal costruttore dell’attributo. Sono consentiti costanti di classe, costanti e tipi scalari, array e decompressioni di array, espressioni booleane e l’operatore di coalescenza nullo.

#[AttributeWithScalarExpression(1 + 1)]
#[AttributeWithClassNameAndConstants(PDO::class, PHP_VERSION_ID)]
#[AttributeWithClassConstant(Http::POST)]
#[AttributeWithBitShift(4 >> 1, 4 << 1)]

Configurazioni degli attributi

E’ possibile configurarli in modo che possano essere utilizzati solo in casi specifici. L’attivazione di questo comportamento avviene passando il flag Attribute sulla classe dell’attributo:

#[Attribute(Attribute::TARGET_CLASS)]
class ClassAttribute
{
}

Sono disponibili i seguenti flag:

Attribute::TARGET_CLASS
Attribute::TARGET_FUNCTION
Attribute::TARGET_METHOD
Attribute::TARGET_PROPERTY
Attribute::TARGET_CLASS_CONSTANT
Attribute::TARGET_PARAMETER
Attribute::TARGET_ALL

E’ possibile combinarli usando l’operazione binaria OR Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION.

Altro flag disponibile riguarda la ripetibilità. Per impostazione predefinita, lo stesso attributo non può essere applicato due volte:

#[Attribute(Attribute::IS_REPEATABLE)]
class ClassAttribute
{
}

Nuovo tipo mixed

RFC https://wiki.php.net/rfc/mixed_type_v2

In PHP 8.0 c’è la possibilità di definire il tipo di variabile come misto, non importa che sia un tipo di ritorno o un tipo dell’argomento della funzione.

<?php
function getValue(mixed $a): mixed {
return $a;
}
getValue(1); // 1

Inoltre, il tipo mixed contiene anche il valore null, quindi non è necessario precederlo con ? per indicare che possa essere nullo.

mixed significa uno di questi tipi:

array
bool
callable
int
float
null
object
resource
string

Sintassi ::class consentita sugli oggetti

RFC https://wiki.php.net/rfc/class_name_literal_on_object

Una piccola, ma utile, nuova miglioria introdotta in PHP 8.0 è la possibilità di usare la sintassi ::class sugli oggetti, invece di dover usare la funzione get_class() su di essi per conoscere il nome della classe.

class Foo {}
$foo = new Foo();
var_dump($foo::class); // string(3) 'Foo'

Nuove funzioni per i tipi string

Se si desidera verificare se una stringa contiene una sottostringa è possibile utilizzare la funzione strpos, che restituisce la posizione della prima occorrenza. In PHP 8.0 c’è una nuova funzione str_contains, che restituisce true se la stringa contiene la sottostringa, altrimenti false.

if (strpos('hello world', 'world') !== false) { /* … */ }
// PHP 8.0
if (str_contains('hello world', 'world')) { /* … */ }

Altre due funzioni sulle stringhe introdotte in PHP 8.0 sono
- str_starts_with('stringa', 'str'): verifica se una specifica stringa inizia con quella indicata
- str_ends_with('stringa', 'ga'): verifica se una specifica stringa finisce con quella indicata.

Per concludere, questo nuovo importante aggiornamento ha portato con sè tutta una serie di ottimizzazioni e potenti funzionalità al linguaggio e in questo articolo ho cercato di illustrare i cambiamenti più interessanti che ci permetteranno di scrivere codice migliore e costruire applicazioni più potenti.

A causa di alcune modifiche sostanziali, esiste la possibilità che sia necessario apportare alcune modifiche al codice per farlo funzionare su PHP 8.

Buon lavoro 👨‍💻

--

--

Maico Orazio
weBeetle

Senior Web Application Developer. I'm a software engineer, a passionate coder, and a web developer. I am a fan of technology. #php #symfony #javascript #reactjs