PHP 8.0: panoramica completa delle novità (1/4)

4 anni fa Da David Grudl  

La versione 8.0 di PHP viene rilasciata proprio in questi giorni. È piena di novità come nessun'altra versione prima. La sua introduzione ha meritato quattro articoli separati. Nel primo daremo un'occhiata alle novità a livello di linguaggio.

Prima di addentrarci nel PHP, ricordiamo che la versione attuale di Nette è completamente preparata per l'ottava versione. Inoltre, come regalo, è stato rilasciato un Nette 2.4 completamente compatibile, quindi dal punto di vista del framework non c'è nulla che vi impedisca di usarlo.

Argomenti denominati

Cominciamo subito con una bomba, che potrebbe essere definita audacemente come una svolta. Gli argomenti possono essere passati a funzioni e metodi non solo in posizione, ma anche in base al loro nome. Il che è assolutamente interessante nel caso in cui un metodo abbia davvero troppi parametri:

class Response implements IResponse
{
	public function setCookie(
		string $name,
		string $value,
		string|DateInterface|null $time,
		string $path = null,
		string $domain = null,
		bool $secure = null,
		bool $httpOnly = null,
		string $sameSite = null
	) {
		...
	}
}

I primi due argomenti sono passati in posizione, gli altri con il loro nome: (il nome deve seguire i parametri posizionali)

$response->setCookie('lang', $lang, sameSite: 'None');

// instead of the horrible
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');

Prima dell'introduzione di questa funzionalità, si pensava di creare una nuova API per l'invio dei cookie in Nette, perché il numero di argomenti per setCookie() cresceva molto e la notazione posizionale creava confusione. Ora non è più necessario, perché gli argomenti con nome sono in questo caso l'API più conveniente. L'IDE li segnala e c'è la sicurezza del tipo.

Sono ideali anche per spiegare gli argomenti logici, dove il loro uso non è necessario, ma un semplice true o false non è sufficiente:

// before
$db = $container->getService(Database::class, true);

// now
$db = $container->getService(Database::class, need: true);

I nomi degli argomenti sono ora parte dell'API pubblica. Non è più possibile cambiarli a piacimento. Per questo motivo, anche Nette sta effettuando una verifica per stabilire se tutti gli argomenti hanno un nome adeguato.

Gli argomenti con nome possono essere usati anche in combinazione con i variadici:

function variadics($a, ...$args) {
	dump($args);
}

variadics(a: 1, b: 2, c: 3);
// $args will contain ['b' => 2, 'c' => 3]

L'array $args può ora contenere anche chiavi non numeriche, il che rappresenta una sorta di rottura del BC. Lo stesso vale per il comportamento di call_user_func_array($func, $args), dove le chiavi dell'array $args giocano ora un ruolo molto più significativo. Al contrario, le funzioni della famiglia func_*() sono protette dagli argomenti con nome.

Gli argomenti denominati sono strettamente correlati al fatto che l'operatore splat ... può ora espandere array associativi:

variadics(...['b' => 2, 'c' => 3]);

Sorprendentemente, al momento non funziona all'interno degli array:

$arr = [ ...['a' => 1, 'b' => 2] ];
// Fatal error: Cannot unpack array with string keys

La combinazione di argomenti denominati e variadici offre la possibilità di avere finalmente una sintassi fissa, ad esempio per il metodo link(), al quale si possono passare argomenti denominati e posizionali:

// before
$presenter->link('Product:detail', $id, 1, 2);
$presenter->link('Product:detail', [$id, 'page' => 1]); // had to be an array

// now
$presenter->link('Product:detail', $id, page: 1);

La sintassi per gli argomenti denominati è molto più sexy della scrittura di array, per cui Latte l'ha subito adottata, dove può essere utilizzata, ad esempio, nei tag {include} e {link}:

{include 'file.latte' arg1: 1, arg2: 2}
{link default page: 1}

Torneremo a parlare di argomenti con nome nella terza parte della serie, in relazione agli attributi.

Un'espressione può lanciare un'eccezione

Lanciare un'eccezione è ora un'espressione. Si può avvolgere tra parentesi e aggiungere a una condizione if. Non sembra molto pratico. Tuttavia, questo è molto più interessante:

// before
if (!isset($arr['value'])) {
	throw new \InvalidArgumentException('value not set');
}
$value = $arr['value'];


// now, when throw is an expression
$value = $arr['value'] ?? throw new \InvalidArgumentException('value not set');

Poiché finora le funzioni freccia possono contenere solo un'espressione, ora possono lanciare eccezioni grazie a questa caratteristica:

// only single expression
$fn = fn() => throw new \Exception('oops');

Espressione di corrispondenza

L'istruzione switch-case ha due difetti principali:

  • utilizza un confronto non rigoroso == al posto di ===
  • bisogna fare attenzione a non dimenticare per errore break

Per questo motivo, PHP offre un'alternativa sotto forma di una nuova espressione match, che utilizza un confronto rigoroso e, al contrario, non utilizza break.

switch esempio di codice:

switch ($statusCode) {
    case 200:
    case 300:
        $message = $this->formatMessage('ok');
        break;
    case 400:
        $message = $this->formatMessage('not found');
        break;
    case 500:
        $message = $this->formatMessage('server error');
        break;
    default:
        $message = 'unknown status code';
        break;
}

E lo stesso (solo con un confronto rigoroso) scritto usando match:

$message = match ($statusCode) {
    200, 300 => $this->formatMessage('ok'),
    400 => $this->formatMessage('not found'),
    500 => $this->formatMessage('server error'),
    default => 'unknown status code',
};

Si noti che match non è una struttura di controllo come switch, ma un'espressione. Nell'esempio, assegniamo il suo valore risultante a una variabile. Allo stesso tempo, anche le singole “opzioni” sono espressioni, quindi non è possibile scrivere più passaggi, come nel caso di switch.

In caso di mancata corrispondenza (e non esiste una clausola di default), viene lanciata l'eccezione UnhandledMatchError.

A proposito, in Latte esistono anche i tag {switch}, {case} e {default}. La loro funzione corrisponde esattamente al nuovo match. Utilizzano un confronto rigoroso, non richiedono break ed è possibile specificare più valori separati da virgole in case.

Operatore Nullsafe

Il concatenamento opzionale consente di scrivere un'espressione la cui valutazione si interrompe se incontra null. Questo grazie al nuovo operatore ?->. Sostituisce molto codice che altrimenti dovrebbe verificare ripetutamente la presenza di null:

$user?->getAddress()?->street

// approximately translates to
$user !== null && $user->getAddress() !== null
	? $user->getAddress()->street
	: null

Perché “approssimativamente”? Perché in realtà l'espressione viene valutata in modo più ingegnoso, in modo che nessun passaggio venga ripetuto. Ad esempio, $user->getAddress() viene richiamato una sola volta, quindi il problema causato dal metodo che restituisce qualcosa di diverso la prima e la seconda volta non si presenta.

Questa caratteristica è stata introdotta da Latte un anno fa. Ora è lo stesso PHP ad adottarla. Ottimo.

Promozione della proprietà del costruttore

Uno zucchero sintattico che risparmia di scrivere due volte il tipo e quattro volte la variabile. È un peccato che non sia arrivato in un momento in cui non avevamo IDE così intelligenti che oggi lo scrivono per noi 🙂

class Facade
{
	private Nette\Database\Connection $db;
	private Nette\Mail\Mailer $mailer;

	public function __construct(Nette\Database\Connection $db, Nette\Mail\Mailer $mailer)
	{
		$this->db = $db;
		$this->mailer = $mailer;
	}
}
class Facade
{
	public function __construct(
		private Nette\Database\Connection $db,
		private Nette\Mail\Mailer $mailer,
	) {}
}

Funziona con Nette DI, potete iniziare a usarlo.

Controlli di tipo più severi per gli operatori aritmetici e bitwise

Ciò che una volta aiutava i linguaggi di scripting dinamici a salire alla ribalta è diventato il loro punto debole. Una volta, PHP si sbarazzava delle “virgolette magiche”, della registrazione delle variabili globali e ora il comportamento rilassato viene sostituito dalla rigidità. Il tempo in cui in PHP si poteva aggiungere, moltiplicare ecc. quasi tutti i tipi di dati per i quali non aveva senso, è passato da tempo. A partire dalla versione 7.0, PHP sta diventando sempre più rigoroso e dalla versione 8.0, il tentativo di utilizzare qualsiasi operatore aritmetico/bitwise su array, oggetti o risorse termina con un TypeError. L'eccezione è rappresentata dalle aggiunte di array.

// arithmetic and bitwise operators
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:

Confronti più sani tra stringhe e numeri

Oppure rendere di nuovo grande l'operatore loose.

Sembrerebbe che non ci sia più spazio per l'operatore loose ==, che sia solo un errore di battitura quando si scrive ===, ma questa modifica lo riporta di nuovo nella mappa. Se lo abbiamo già, lasciamo che si comporti in modo ragionevole. Come risultato del precedente paragone “irragionevole”, per esempio, in_array() potrebbe trollare in modo spiacevole:

$validValues = ['foo', 'bar', 'baz'];
$value = 0;

dump(in_array($value, $validValues));
// surprisingly returned true
// since PHP 8.0 returns false

Il cambiamento di comportamento di == riguarda il confronto tra numeri e stringhe “numeriche” ed è mostrato nella tabella seguente:

Confronto Prima PHP 8.0
0 == "0" vero vero
0 == "0.0" vero vero
0 == "foo" vero falso
0 == "" vero falso
42 == " 42" vero vero
42 == "42 " vero vero
42 == "42foo" vero falso
42 == "abc42" falso falso
"42" == " 42" vero vero
"42" == "42 " falso vero

Sorprendentemente, si tratta di un'interruzione del BC nel cuore della lingua, che è stata approvata senza alcuna resistenza. E questo è un bene. JavaScript potrebbe essere molto geloso di questo aspetto.

Segnalazione degli errori

Molte funzioni interne ora attivano TypeError e ValueError invece di avvertimenti che erano facili da ignorare. Alcuni avvisi del kernel sono stati riclassificati. L'operatore di chiusura @ ora non silenzia gli errori fatali. E PDO lancia eccezioni per impostazione predefinita.

Nette ha sempre cercato di risolvere questi problemi in qualche modo. Tracy ha modificato il comportamento dell'operatore shutup, Database ha cambiato il comportamento di PDO, Utils contiene funzioni sostitutive di quelle standard, che lanciano eccezioni invece di avvisi facili da perdere, ecc. È bello vedere che la direzione rigorosa che Nette ha nel suo DNA diventa la direzione nativa del linguaggio.

Incrementi negativi delle chiavi degli array

$arr[-5] = 'first';
$arr[] = 'second';

Quale sarà la chiave del secondo elemento? Una volta era 0, since PHP 8 it’s -4.

Virgola finale

L'ultimo punto in cui la virgola non poteva essere presente era la definizione degli argomenti delle funzioni. Questo appartiene al passato:

	public function __construct(
		Nette\Database\Connection $db,
		Nette\Mail\Mailer $mailer, // trailing comma
	) {
		....
	}

$object::class

La costante magica ::class funziona anche con gli oggetti $object::class, sostituendo completamente la funzione get_class().

Cattura le eccezioni solo per tipo

Infine, non è necessario specificare una variabile per l'eccezione nella clausola catch:

try {
	$container->getService(Database::class);

} catch (MissingServiceException) {  // no $e
	$logger->log('....');
}

Nelle prossime parti, vedremo le principali innovazioni riguardanti i tipi di dati, mostreremo cosa sono gli attributi, quali nuove funzioni e classi sono apparse in PHP e introdurremo il compilatore Just in Time.