PHP 8.0: nuove funzioni, classi e JIT (4/4)

3 anni fa Da David Grudl  

È stata rilasciata la versione 8.0 di PHP. È ricca di nuove funzionalità come nessun'altra versione prima. La loro introduzione ha meritato quattro articoli separati. Nell'ultima parte daremo un'occhiata alle nuove funzioni e classi e introdurremo il compilatore Just in Time.

Nuove funzioni

La libreria standard di PHP conta centinaia di funzioni e nella versione 8.0 ne sono apparse sei nuove. Non sembra molto, ma la maggior parte di esse rimedia ai punti deboli del linguaggio. Il che è in linea con l'intero concetto della versione 8.0, che rende il PHP più compatto e consolidato come nessuna versione prima d'ora. Una panoramica di tutte le nuove funzioni e metodi si trova nella Guida alla migrazione.

str_contains() str_starts_with() str_ends_with()

Funzioni per determinare se una stringa inizia, finisce o contiene una sottostringa.

if (str_contains('Nette', 'te')) {
	...
}

Con l'avvento di questa trinità, PHP definisce come gestire una stringa vuota durante la ricerca, che è ciò a cui aderiscono tutte le altre funzioni correlate, e cioè una stringa vuota si trova ovunque:

str_contains('Nette', '')     // true
str_starts_with('Nette', '')  // true
strpos('Nette', '')           // 0 (previously false)

Grazie a questo, il comportamento della trinità è completamente identico a quello degli analoghi di Nette:

str_contains()      # Nette\Utils\String::contains()
str_starts_with()   # Nette\Utils\String::startsWith()
str_ends_with()     # Nette\Utils\String::endsWith()

Perché queste funzioni sono così importanti? Le librerie standard di tutti i linguaggi sono sempre appesantite dallo sviluppo storico; le incoerenze e i passi falsi non possono essere evitati. Ma allo stesso tempo sono una testimonianza del rispettivo linguaggio. Sorprendentemente, il PHP di 25 anni fa manca di funzioni per operazioni di base come la restituzione del primo o dell'ultimo elemento di un array, l'escape dell'HTML senza brutte sorprese (htmlspecialchars non esegue l'escape di un apostrofo) o la semplice ricerca di una stringa in una stringa. Non è detto che possa essere in qualche modo aggirato, perché il risultato non è un codice leggibile e comprensibile. Questa è una lezione per tutti gli autori di API. Quando si vede che gran parte della documentazione della funzione è occupata da spiegazioni di insidie (come i valori di ritorno di strpos), è un chiaro segnale per modificare la libreria e aggiungere str_contains.

get_debug_type()

Sostituisce l'ormai obsoleto get_type(). Al posto dei tipi lunghi, come integer, restituisce l'attuale int, mentre nel caso degli oggetti restituisce direttamente il tipo:

Value gettype() get_debug_type()
'abc' string string
[1, 2] array array
231 integer int
3.14 double float
true boolean bool
null NULL null
new stdClass object stdClass
new Foo\Bar object Foo\Bar
function() {} object Closure
new class {} object class@anonymous
new class extends Foo {} object Foo@anonymous
curl_init() resource resource (curl)
curl_close($ch) resource (closed) resource (closed)

Migrazione da risorsa a oggetto

I valori del tipo di risorsa derivano da un'epoca in cui PHP non aveva ancora gli oggetti, ma ne aveva effettivamente bisogno. È così che sono nate le risorse. Oggi abbiamo gli oggetti e, rispetto alle risorse, funzionano molto meglio con il garbage collector, quindi il piano è di sostituirli gradualmente tutti con gli oggetti.

A partire da PHP 8.0, le risorse immagini, curl join, openssl, xml, ecc. sono state trasformate in oggetti. In PHP 8.1, seguiranno le connessioni FTP e così via.

$res = imagecreatefromjpeg('image.jpg');
$res instanceof GdImage  // true
is_resource($res)        // false - BC break

Questi oggetti non hanno ancora metodi, né si possono istanziare direttamente. Finora si tratta solo di eliminare le risorse obsolete da PHP senza modificare l'API. E questo è un bene, perché creare una buona API è un compito separato e impegnativo. Nessuno desidera la creazione di nuove classi PHP come SplFileObject con metodi chiamati fgetc() o fgets().

PhpToken

Anche il tokenizer e le funzioni intorno a token_get_all sono migrate a oggetti. Questa volta non si tratta di sbarazzarsi delle risorse, ma di ottenere un oggetto completo che rappresenta un token PHP.

<?php
$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0];         // instance PhpToken

echo $token->id;             // T_OPEN_TAG
echo $token->text;           // '<?php'
echo $token->line;           // 1
echo $token->getTokenName(); // 'T_OPEN_TAG'
echo $token->is(T_STRING);   // false
echo $token->isIgnorable();  // true

Il metodo isIgnorable() restituisce true per i token T_WHITESPACE, T_COMMENT, T_DOC_COMMENT e T_OPEN_TAG.

Mappe deboli

Le mappe deboli sono legate al garbage collector, che rilascia dalla memoria tutti gli oggetti e i valori che non sono più utilizzati (cioè non ci sono variabili o proprietà che li contengono). Poiché i thread PHP hanno vita breve e la memoria disponibile sui nostri server è abbondante, di solito non ci occupiamo affatto di questioni relative alla liberazione efficace della memoria. Ma per gli script di lunga durata, sono essenziali.

L'oggetto WeakMap è simile a SplObjectStorage Entrambi usano oggetti come chiavi e permettono di memorizzare valori arbitrari sotto di essi. La differenza è che WeakMap non impedisce all'oggetto di essere rilasciato dal garbage collector. Cioè, se l'unico posto in cui l'oggetto esiste attualmente è una chiave nella mappa debole, verrà rimosso dalla mappa e dalla memoria.

$map = new WeakMap;
$obj = new stdClass;
$map[$obj]  = 'data for $obj';

dump(count($map));  // 1
unset($obj);
dump(count($map));  // 0

A cosa serve? Per esempio, per la cache. Abbiamo un metodo loadComments() a cui passiamo un articolo del blog e che restituisce tutti i suoi commenti. Poiché il metodo viene chiamato ripetutamente per lo stesso articolo, creeremo un altro getComments(), che metterà in cache il risultato del primo metodo:

class Comments
{
	private WeakMap $cache;

	public function __construct()
	{
		$this->cache = new WeakMap;
	}

	public function getComments(Article $article): ?array
	{
		$this->cache[$article] ??= $this->loadComments($article);
		return $this->cache[$article]
	}

	...
}

Il punto è che quando l'oggetto $article viene rilasciato (per esempio, l'applicazione inizia a lavorare con un altro articolo), anche la sua voce viene rilasciata dalla cache.

PHP JIT (compilatore Just in Time)

Forse sapete che PHP viene compilato in cosiddetti opcode, ovvero istruzioni di basso livello che potete vedere qui, per esempio e che vengono eseguite da una macchina virtuale PHP. E cos'è un JIT? Il JIT può compilare in modo trasparente PHP direttamente in codice macchina, che viene eseguito direttamente dal processore, in modo da evitare l'esecuzione più lenta da parte della macchina virtuale.

Il JIT ha quindi lo scopo di velocizzare PHP.

Lo sforzo di implementare JIT in PHP risale al 2011 ed è sostenuto da Dmitry Stogov. Da allora, ha provato 3 diverse implementazioni, ma nessuna di queste è stata inserita nella versione finale di PHP per tre motivi: il risultato non è mai stato un aumento significativo delle prestazioni per le applicazioni web tipiche; complica la manutenzione di PHP (cioè nessuno, a parte Dmitry, lo capisce 😉 ); c'erano altri modi per migliorare le prestazioni senza dover usare un JIT.

L'aumento delle prestazioni osservato nella versione 7 di PHP è stato un sottoprodotto del lavoro sul JIT, anche se paradossalmente non è stato implementato. Questo sta accadendo solo ora in PHP 8. Ma non nutro aspettative esagerate: probabilmente non vedrete alcun aumento di velocità.

Perché il JIT è entrato in PHP? Innanzitutto, gli altri modi per migliorare le prestazioni si stanno lentamente esaurendo e il JIT è semplicemente il passo successivo. Nelle comuni applicazioni web, non porta alcun miglioramento della velocità, ma accelera in modo significativo, ad esempio, i calcoli matematici. Questo apre la possibilità di iniziare a scrivere queste cose in PHP. In effetti, sarebbe possibile implementare direttamente in PHP alcune funzioni che in precedenza richiedevano un'implementazione diretta in C a causa della velocità.

Il JIT fa parte dell'estensione opcache ed è abilitato insieme ad essa in php.ini (leggere la documentazione su quelle quattro cifre):

zend_extension=php_opcache.dll
opcache.jit=1205              ; configuration using four digits OTRC
opcache.enable_cli=1          ; in order to work in the CLI as well
opcache.jit_buffer_size=128M  ; dedicated memory for compiled code

È possibile verificare che JIT sia in esecuzione, per esempio, nel pannello informativo della barra Tracy.

Il JIT funziona molto bene se tutte le variabili hanno tipi chiaramente definiti e non possono cambiare anche quando si richiama ripetutamente lo stesso codice. Mi chiedo quindi se un giorno anche in PHP dichiareremo i tipi delle variabili: string $s = 'Bye, this is the end of the series';