PHP 8.0: Nove funkcije, razredi in JIT (4/4)

pred 4 leti od David Grudl  

Izšla je različica PHP 8.0. Tako je polna novosti, kot še nobena različica pred njo. Njihova predstavitev je zahtevala kar štiri ločene članke. V tem zadnjem si bomo pogledali nove funkcije in razrede ter predstavili Just in Time Compiler.

Nove funkcije

Standardna knjižnica PHP ima na stotine funkcij in v različici 8.0 se je pojavilo šest novih. Videti je kot malo, vendar večina od njih zapolnjuje šibke točke jezika. Kar se lepo ujema z vzdušjem celotne različice 8.0, ki dokončuje in konsolidira PHP kot nobena različica pred njo. Pregled vseh novih funkcij in metod najdete v migration guide.

str_contains() str_starts_with() str_ends_with()

Funkcije za ugotavljanje, ali se niz začne, konča ali vsebuje podniz.

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

Skupaj s prihodom te trojice PHP definira, kako ravnati s praznim nizom pri iskanju, po čemer se ravnajo tudi vse ostale povezane funkcije, in sicer tako, da se prazen niz nahaja povsod:

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

Zahvaljujoč temu je obnašanje trojice popolnoma identično analogijam v Nette:

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

Zakaj so te funkcije tako pomembne? Standardne knjižnice vseh jezikov so vedno obremenjene z zgodovinskim razvojem in ni se mogoče izogniti nastanku nekonsistentnosti in napak. Ampak hkrati gre za vizitko dotičnega jezika. Presenetljivo je, ko pri 25 let starem PHP manjkajo funkcije za tako osnovne operacije, kot je vračanje prvega ali zadnjega elementa iz polja, ubežanje znakov v HTML brez prevar (htmlspecialchars ne ubeži apostrofa), ali prav iskanje niza v nizu. Da se to da nekako zaobiti, ne vzdrži, ker rezultat potem ni čitljiva in razumljiva koda. To je nauk za vse avtorje API-jev. Ko vidite, da znaten del dokumentacije funkcije zavzema razlaga zank (kot na primer povratne vrednosti strpos), je to jasen signal, da knjižnico prilagodite in dopolnite prav z str_contains.

get_debug_type()

Nadomešča že zastarelo get_type(). Namesto dolgih tipov kot integer vrača danes uporabljane int, v primeru objektov vrača kar tip:

Vrednost 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)

Objektivizacija virov

Vrednosti tipa resource izvirajo iz časov, ko PHP še ni imel objektov, ampak jih je pravzaprav potreboval. Tako so prišli na svet resources. Danes imamo objekte in v primerjavi z resources delujejo veliko bolje z garbage collectorjem, zato je v načrtu postopoma vse nadomestiti z objekti.

Od PHP 8.0 se spreminjajo v objekte resources slik, povezav curl, openssl, xml itd.. V PHP 8.1 pridejo na vrsto FTP povezave itd.

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

Ti objekti zaenkrat nimajo nobenih metod, niti ne morete neposredno ustvarjati njihovih instanc. Gre zaenkrat resnično le za to, da se iz PHP odstranijo zastareli resources brez spremembe API-ja. In to je dobro, ker je ustvarjanje dobrega API-ja samostojna in zahtevna naloga. Nihče si ne želi, da bi v PHP nastali dodatni razredi kot SplFileObject z metodami, poimenovanimi fgetc() ali fgets().

PhpToken

V objekte se seli tudi tokenizer in torej funkcije okoli token_get_all. Tokrat ne gre za znebljanje resources, ampak dobivamo polnovreden objekt, ki predstavlja en PHP token.

<?php

$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0];         // instanca 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() vrne true za tokene T_WHITESPACE, T_COMMENT, T_DOC_COMMENT in T_OPEN_TAG.

Weak maps

Weak mape so povezane z gargabe kolektorjem, ki iz pomnilnika sprošča vse objekte in vrednosti, ki se ne uporabljajo več (tj. ni nobene uporabljene spremenljivke, či lastnosti, ki bi jih vsebovala). Ker je življenje PHP niti je kratkotrajno in pomnilnika imamo danes na strežnikih na voljo dovolj, vprašanja okoli učinkovitega sproščanja pomnilnika praviloma sploh ne rešujemo. Ampak pri dolgotrajnejših skriptih so bistvena.

Objekt WeakMap je podoben SplObjectStorage. V obeh se kot ključi uporabljajo objekti in omogočajo pod njimi shranjevanje poljubnih vrednosti. Razlika je v tem, da WeakMap ne prepreči temu, da bi bil objekt sproščen garbage kolektorjem. Tj. če edino mesto, kjer se še objekt pojavlja, je ključ v weak mapi, bo iz mape in pomnilnika odstranjen.

$map = new WeakMap;
$obj = new stdClass;
$map[$obj]  = 'podatki za $obj';

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

Za kaj je to dobro? Na primer za predpomnjenje. Imejmo metodo loadComments(), kateri posredujemo članek na blogu in ona vrne vse njegove komentarje. Ker se metoda kliče z istim člankom ponavljajoče, ustvarimo si še getComments(), ki bo rezultat prve metode predpomnila:

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

	...
}

Šala je v tem, da v trenutku, ko se sprosti objekt $article (na primer aplikacija začne delati z drugim člankom), se sprosti tudi njegov element iz predpomnilnika.

PHP JIT (Just in Time Compiler)

Morda veste, da se PHP prevaja v t.i. opcode, kar so nizkonivojska navodila, ki si jih lahko ogledate na primer tukaj in ki jih izvaja virtualni stroj PHP. In kaj je JIT? JIT lahko transparentno prevaja PHP neposredno v strojno kodo, ki jo izvaja neposredno procesor, tako da se obejde počasnejše izvajanje z virtualnim strojem.

JIT naj bi torej pospešil PHP.

Prizadevanja za implementacijo JIT v PHP segajo vse do leta 2011 in za njimi stoji Dmitry Stogov. Od takrat je preizkusil 3 različne implementacije, vendar nobena od njih ni prišla v ostro PHP in to iz teh razlogov: rezultatom nikoli ni bil noben bistven porast zmogljivosti za tipične spletne aplikacije; otežuje vzdrževanje PHP (tj. nihče razen Dmitryja tega ne razume 😉); obstajale so druge poti, kako izboljšati zmogljivost, ne da bi se moralo uporabljati JIT.

Skokovito povečanje zmogljivosti PHP v različici 7 je bilo stranskim produktom dela na JIT, čeprav do njegovega nasajenja paradoksalno ni prišlo. K temu prihaja šele v PHP 8. Ampak takoj bom zaviral pretirana pričakovanja: verjetno nobenega pospeška ne boste zaznali.

Zakaj torej JIT vstopa v PHP? Prvič, druge poti za izboljšanje zmogljivosti počasi zmanjkujejo in JIT je preprosto na vrsti. V običajnih spletnih aplikacijah sicer pospeška ne prinaša, ampak bistveno pospešuje na primer matematične izračune. Odpira se tako možnost, da se te stvari začnejo pisati v PHP. In pravzaprav bi bilo tako mogoče neposredno v PHP implementirati funkcije, ki so doslej zahtevale implementacijo neposredno v C zaradi hitrosti.

JIT je del razširitve opcache in se vklopi skupaj z njo v php.ini (preberite si dokumentacijo k oné čtveřici číslic):

zend_extension=php_opcache.dll
opcache.jit=1205              ; konfiguracija s pomočjo četverice OTRC
opcache.enable_cli=1          ; da deluje tudi v CLI
opcache.jit_buffer_size=128M  ; rezerviran pomnilnik za prevedeno kodo

Da JIT teče se boste izvedeli na primer v informacijski plošči v Tracy Baru.

JIT zelo dobro deluje takrat, ko vse spremenljivke imajo jasno dane tipe in se ne morejo spreminjati pri ponovnem klicu iste kode. Sem zato radoveden, ali bomo nekoč v PHP deklarirali tipe tudi pri spremenljivkah: string $s = 'Zdravo, tole je zaključek serije';