PHP 8.0: Noi funcții, clase și JIT (4/4)

acum 4 ani De la David Grudl  

A fost lansată versiunea 8.0 a PHP. Este plină de funcții noi, cum nu a mai fost nicio altă versiune până acum. Prezentarea lor a meritat patru articole separate. În ultima parte vom arunca o privire asupra noilor funcții și clase și vom prezenta Compilatorul Just in Time.

Funcții noi

Biblioteca standard PHP are sute de funcții, iar în versiunea 8.0 au apărut șase funcții noi. Nu pare mult, dar cele mai multe dintre ele remediază punctele slabe ale limbajului. Ceea ce se aliniază foarte bine cu întregul concept al versiunii 8.0, care strânge și consolidează PHP ca nicio altă versiune anterioară. O prezentare generală a tuturor noilor funcții și metode poate fi găsită în ghidul de migrare.

str_contains() str_starts_with() str_ends_with()

Funcții pentru a determina dacă un șir de caractere începe, se termină sau conține o subșir.

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

Odată cu apariția acestei trinități, PHP definește modul de tratare a unui șir gol în timpul căutării, la care aderă toate celelalte funcții aferente, și anume un șir gol se găsește peste tot:

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

Datorită acestui fapt, comportamentul trinității este complet identic cu cel al analogilor Nette:

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

De ce sunt aceste funcții atât de importante? Bibliotecile standard ale tuturor limbajelor sunt întotdeauna împovărate de dezvoltarea istorică; inconsecvențele și pașii greșiți nu pot fi evitați. Dar, în același timp, este o mărturie a limbajului respectiv. În mod surprinzător, PHP, vechi de 25 de ani, nu dispune de funcții pentru operații de bază precum returnarea primului sau ultimului element al unei matrice, scăparea HTML fără surprize neplăcute (htmlspecialchars nu scapă de un apostrof) sau pur și simplu căutarea unui șir de caractere într-un șir de caractere. Nu se susține că poate fi cumva ocolit, pentru că rezultatul nu este un cod lizibil și inteligibil. Aceasta este o lecție pentru toți autorii de API-uri. Când vedeți că o mare parte din documentația funcției este ocupată de explicații ale capcanelor (cum ar fi valorile de returnare ale strpos), este un semn clar de a modifica biblioteca și de a adăuga str_contains.

get_debug_type()

Înlocuiește acum învechitul get_type(). În locul tipurilor lungi, cum ar fi integer, returnează tipul utilizat în prezent int, în cazul obiectelor returnează direct tipul:

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)

Migrarea de la resurse la obiecte

Valorile de tip resursă provin dintr-o perioadă în care PHP nu avea încă obiecte, dar avea nevoie de ele. Așa s-au născut resursele. Astăzi avem obiecte și, în comparație cu resursele, acestea funcționează mult mai bine cu colectorul de gunoi, așa că planul este de a le înlocui treptat pe toate cu obiecte.

Începând cu PHP 8.0, resursele images, curl joins, openssl, xml, etc. sunt schimbate în obiecte. În PHP 8.1, vor urma conexiunile FTP etc.

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

Aceste obiecte nu au încă nicio metodă și nici nu le puteți instanția direct. Până acum, este vorba doar de a scăpa de resursele învechite din PHP fără a modifica API-ul. Și asta este bine, pentru că crearea unui API bun este o sarcină separată și dificilă. Nimeni nu dorește crearea unor noi clase PHP, cum ar fi SplFileObject, cu metode numite fgetc() sau fgets().

PhpToken

Tokenizatorul și funcțiile din jurul token_get_all sunt, de asemenea, migrate în obiecte. De data aceasta nu este vorba de a scăpa de resurse, ci obținem un obiect cu drepturi depline care reprezintă 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

Metoda isIgnorable() returnează true pentru token-urile T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, și T_OPEN_TAG.

Hărți slabe

Hărțile slabe sunt legate de colectorul de gunoi, care eliberează din memorie toate obiectele și valorile care nu mai sunt utilizate (adică nu există nicio variabilă sau proprietate care să le conțină). Deoarece firele de execuție PHP au o durată scurtă de viață și avem multă memorie disponibilă pe serverele noastre, de obicei nu abordăm deloc problemele referitoare la eliberarea efectivă a memoriei. Dar pentru scripturile cu o durată de execuție mai lungă, acestea sunt esențiale.

Obiectul WeakMap este similar cu SplObjectStorage Ambele utilizează obiecte ca chei și permit stocarea de valori arbitrare sub ele. Diferența constă în faptul că WeakMap nu împiedică eliberarea obiectului de către garbage collector. De exemplu, dacă singurul loc în care există în prezent obiectul este o cheie din harta slabă, acesta va fi eliminat din hartă și din memorie.

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

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

La ce este bun? De exemplu, pentru caching. Să avem o metodă loadComments() căreia îi transmitem un articol de blog și care returnează toate comentariile sale. Deoarece metoda este apelată în mod repetat pentru același articol, vom crea o altă metodă getComments(), care va pune în cache rezultatul primei metode:

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]
	}

	...
}

Ideea este că atunci când obiectul $article este eliberat (de exemplu, aplicația începe să lucreze cu un alt articol), intrarea acestuia este, de asemenea, eliberată din memoria cache.

PHP JIT (Just in Time Compiler)

Poate știți că PHP este compilat în așa-numitele opcode, care sunt instrucțiuni de nivel scăzut pe care le puteți vedea aici, de exemplu și care sunt executate de o mașină virtuală PHP. Și ce este un JIT? JIT poate compila în mod transparent PHP direct în cod mașină, care este executat direct de către procesor, astfel încât execuția mai lentă de către mașina virtuală este ocolită.

Prin urmare, JIT este menit să accelereze PHP.

Efortul de a implementa JIT în PHP datează din 2011 și este susținut de Dmitri Stogov. De atunci, el a încercat 3 implementări diferite, dar niciuna dintre ele nu a ajuns într-o versiune finală a PHP din trei motive: rezultatul nu a fost niciodată o creștere semnificativă a performanței pentru aplicațiile web tipice; complică întreținerea PHP (adică nimeni în afară de Dmitry nu o înțelege 😉 ); existau alte modalități de a îmbunătăți performanța fără a fi nevoie să se folosească un JIT.

Saltul de creștere a performanței observat în versiunea 7 a PHP a fost un produs secundar al activității privind JIT, deși, în mod paradoxal, nu a fost implementat. Acest lucru se întâmplă abia acum, în PHP 8. Dar îmi voi reține așteptările exagerate: probabil că nu veți vedea nicio creștere de viteză.

Așadar, de ce intră JIT în PHP? În primul rând, alte modalități de îmbunătățire a performanței se epuizează încet, iar JIT este pur și simplu următorul pas. În aplicațiile web obișnuite, nu aduce îmbunătățiri ale vitezei, dar accelerează semnificativ, de exemplu, calculele matematice. Acest lucru deschide posibilitatea de a începe să scrie aceste lucruri în PHP. De fapt, ar fi posibil să se implementeze direct în PHP unele funcții care anterior necesitau o implementare directă în C din cauza vitezei.

JIT face parte din extensia opcache și este activată împreună cu aceasta în php.ini (citiți documentația despre aceste patru 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

Puteți verifica dacă JIT rulează, de exemplu, în panoul de informații Tracy Bar.

JIT funcționează foarte bine dacă toate variabilele au tipuri clar definite și nu se pot schimba nici măcar atunci când se apelează același cod în mod repetat. Prin urmare, mă întreb dacă nu cumva într-o bună zi vom declara tipuri în PHP și pentru variabile: string $s = 'Bye, this is the end of the series';