PHP 8.0: Funcții noi, clase și JIT (4/4)
A fost lansată versiunea PHP 8.0. Este atât de plină de noutăți cum nu a mai fost nicio versiune înainte. Prezentarea lor a necesitat patru articole separate. În acest ultim articol, vom analiza funcțiile și clasele noi și vom prezenta Just in Time Compiler.

Funcții noi
Biblioteca standard PHP dispune de sute de funcții, iar în versiunea 8.0 au apărut șase noi. Pare puțin, dar majoritatea dintre ele acoperă punctele slabe ale limbajului. Ceea ce corespunde frumos cu tonul întregii versiuni 8.0, care definitivează și consolidează PHP ca nicio versiune anterioară. O prezentare generală a tuturor funcțiilor și metodelor noi o găsiți în ghidul de migrare.
str_contains()
str_starts_with()
str_ends_with()
Funcții pentru a determina dacă un șir începe, se termină sau conține un subșir.
if (str_contains('Nette', 'te')) {
...
}
Odată cu apariția acestui trio, PHP definește cum să se trateze șirul gol la căutare, conform căruia se ghidează și toate celelalte funcții conexe, și anume că șirul gol se găsește peste tot:
str_contains('Nette', '') // true
str_starts_with('Nette', '') // true
strpos('Nette', '') // 0 (anterior false)
Datorită acestui fapt, comportamentul trio-ului este complet identic cu echivalentele din 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ă și
nu se poate evita apariția inconsecvențelor și a pașilor greșiți. Dar, în
același timp, este cartea de vizită a limbajului respectiv. Este surprinzător
când la PHP, vechi de 25 de ani, lipsesc funcții pentru operații atât de
fundamentale, cum ar fi returnarea primului sau ultimului element dintr-un
array, escaparea HTML fără capcane (htmlspecialchars
nu
escapează apostroful) sau tocmai căutarea unui șir într-un șir. Faptul că
se poate ocoli cumva nu rezistă, deoarece rezultatul nu este un cod
lizibil și inteligibil. Este o lecție pentru toți autorii de API-uri. Când
vedeți că o parte considerabilă a documentației unei funcții este ocupată
de explicarea subtilităților (cum ar fi valorile returnate de
strpos
), este un semnal clar pentru a modifica biblioteca și a
adăuga tocmai str_contains
.
get_debug_type()
Înlocuiește deja învechitul get_type()
. În loc de tipuri
lungi precum integer
, returnează int
utilizat
astăzi, în cazul obiectelor returnează direct tipul:
Valoare | 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) |
Obiectivizarea resurselor
Valorile de tip resource provin din vremurile în care PHP nu avea încă obiecte, dar de fapt avea nevoie de ele. Așa au apărut resursele. Astăzi avem obiecte și, spre deosebire de resurse, funcționează mult mai bine cu garbage collector-ul, așa că planul este să le înlocuim treptat pe toate cu obiecte.
De la PHP 8.0, resursele imagini, conexiuni curl, openssl, xml, etc. se transformă î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 deocamdată nicio metodă și nici nu puteți crea
direct instanțele lor. Este vorba deocamdată doar de a scoate din PHP
resursele învechite fără a schimba API-ul. Și asta este bine, deoarece
crearea unui API bun este o sarcină separată și dificilă. Nimeni nu
dorește ca în PHP să apară alte clase precum SplFileObject cu metode
denumite fgetc()
sau fgets()
.
PhpToken
Și tokenizerul se mută în obiecte, deci funcțiile din jurul
token_get_all
. De data aceasta nu este vorba de eliminarea
resurselor, ci primim un obiect complet care reprezintă un token PHP.
<?php
$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0]; // instanță 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 tokenurile
T_WHITESPACE
, T_COMMENT
, T_DOC_COMMENT
și T_OPEN_TAG
.
Weak maps
Weak map-urile sunt legate de garbage collector, care eliberează din memorie toate obiectele și valorile care nu mai sunt utilizate (adică nu există nicio variabilă sau proprietate utilizată care să le conțină). Deoarece viața unui fir de execuție PHP este efemeră și astăzi avem suficientă memorie disponibilă pe servere, de obicei nu ne preocupăm deloc de problemele legate de eliberarea eficientă a memoriei. Dar pentru scripturile care rulează pe termen lung, acestea sunt esențiale.
Obiectul WeakMap
este similar cu SplObjectStorage
.
În ambele, obiectele sunt utilizate ca chei și permit stocarea oricăror
valori sub ele. Diferența este că WeakMap
nu împiedică
eliberarea obiectului de către garbage collector. Adică, dacă singurul loc
în care obiectul mai există este cheia într-un weak map, acesta va fi
eliminat din mapă și din memorie.
$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 'date pentru $obj';
dump(count($map)); // 1
unset($obj);
dump(count($map)); // 0
La ce este bun acest lucru? De exemplu, pentru caching. Să presupunem că
avem o metodă loadComments()
, căreia îi transmitem un articol
de blog și ea returnează toate comentariile sale. Deoarece metoda este
apelată în mod repetat cu același articol, vom crea și
getComments()
, care va stoca î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ă în momentul în care obiectul $article
este
eliberat (de exemplu, aplicația începe să lucreze cu alt articol), se
eliberează și intrarea sa din cache.
PHP JIT (Just in Time Compiler)
Poate știți că PHP se compilează în așa-numitul opcode, care sunt instrucțiuni de nivel scăzut, pe care le puteți vizualiza, de exemplu, aici și care sunt executate de mașina virtuală PHP. Și ce este JIT? JIT poate compila transparent PHP direct în cod mașină, care este executat direct de procesor, ocolind astfel execuția mai lentă de către mașina virtuală.
Deci, JIT ar trebui să accelereze PHP.
Efortul de a implementa JIT în PHP datează din 2011 și îi aparține lui Dmitry Stogov. De atunci, a încercat 3 implementări diferite, dar niciuna dintre ele nu a ajuns în PHP-ul oficial din următoarele 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 înțelege 😉); existau alte modalități de a îmbunătăți performanța fără a fi nevoie să se utilizeze JIT.
Creșterea bruscă a performanței PHP în versiunea 7 a fost un produs secundar al lucrului la JIT, deși, paradoxal, nu a fost implementat. Acest lucru se întâmplă abia în PHP 8. Dar voi tempera imediat așteptările exagerate: probabil nu veți observa nicio accelerare.
De ce intră atunci JIT în PHP? Pe de o parte, alte căi de îmbunătățire a performanței se epuizează încet, iar JIT este pur și simplu la rând. Deși nu aduce accelerare în aplicațiile web obișnuite, accelerează semnificativ, de exemplu, calculele matematice. Astfel, se deschide posibilitatea de a începe să scriem aceste lucruri în PHP. Și, de fapt, ar fi posibil să implementăm direct în PHP funcții care până acum necesitau implementare direct în C din cauza vitezei.
JIT face parte din extensia opcache
și se activează împreună
cu aceasta în php.ini (citiți documentația
despre cele patru cifre):
zend_extension=php_opcache.dll
opcache.jit=1205 ; configurare folosind cvartetul OTRC
opcache.enable_cli=1 ; pentru a funcționa și în CLI
opcache.jit_buffer_size=128M ; memorie alocată pentru codul compilat
Că JIT rulează veți afla, de exemplu, în panoul informativ din Tracy Bar.
JIT funcționează foarte bine atunci când toate variabilele au tipuri clar
definite și nu se pot schimba la apelarea repetată a aceluiași cod. Sunt
curios, așadar, dacă vom declara odată în PHP tipuri și pentru
variabile: string $s = 'Salut, acesta este finalul serialului';
Pentru a trimite un comentariu, vă rugăm să vă conectați