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

pred 3 leti od David Grudl  

Izdana je bila različica PHP 8.0. Polna je novih funkcij, kot jih ni imela še nobena druga različica. Njihova predstavitev je zaslužna za štiri ločene članke. V zadnjem delu si bomo ogledali nove funkcije in razrede ter predstavili prevajalnik Just in Time.

Nove funkcije

Standardna knjižnica PHP ima več sto funkcij, v različici 8.0 pa se je pojavilo šest novih. Ne zdi se veliko, vendar večina od njih odpravlja šibke točke jezika. Kar se lepo sklada s celotnim konceptom različice 8.0, ki je poostrila in utrdila PHP kot še nobena različica doslej. Pregled vseh novih funkcij in metod je na voljo v migracijskem vodniku.

str_contains() str_starts_with() str_ends_with()

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

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

S pojavom te trojice PHP določa, kako ravnati s praznim nizom med iskanjem, kar upoštevajo vse druge povezane funkcije, in sicer prazen niz se najde povsod:

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

Zahvaljujoč temu je obnašanje te trojice popolnoma enako obnašanju analogov 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; nedoslednostim in napačnim korakom se ni mogoče izogniti. Hkrati pa so tudi pričevanje o posameznem jeziku. Presenetljivo je, da 25 let star PHP nima funkcij za tako osnovne operacije, kot so vračanje prvega ali zadnjega elementa polja, pobeg HTML brez neprijetnih presenečenj (htmlspecialchars ne pobegne apostrofu) ali samo iskanje niza v nizu. Ne drži, da ga je mogoče nekako zaobiti, saj rezultat ni čitljiva in razumljiva koda. To je lekcija za vse avtorje API. Ko vidite, da večino dokumentacije funkcije zavzemajo razlage pasti (kot so povratne vrednosti funkcije strpos), je to jasen znak, da je treba spremeniti knjižnico in dodati str_contains.

get_debug_type()

Nadomešča zdaj že zastarelo get_type(). Namesto dolgih tipov, kot je integer, vrne danes uporabljenega int, v primeru predmetov pa neposredno vrne tip:

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)

Migracija virov v objekte

Vrednosti vrste virov izvirajo iz časov, ko PHP še ni imel objektov, vendar jih je dejansko potreboval. Tako so se rodili viri. Danes imamo predmete, ki v primerjavi z viri veliko bolje delujejo s pobiralnikom smeti, zato jih nameravamo postopoma vse nadomestiti s predmeti.

Od različice PHP 8.0 so viri slike, curl, openssl, xml itd spremenjeni v objekte. V PHP 8.1 bodo sledile povezave FTP itd.

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

Ti predmeti še nimajo metod, prav tako jih ne morete neposredno instantizirati. Zaenkrat gre le za to, da se znebimo zastarelih virov iz PHP, ne da bi spremenili API. In to je dobro, saj je ustvarjanje dobrega API ločena in zahtevna naloga. Nihče si ne želi ustvarjanja novih razredov PHP, kot je SplFileObject, z metodami z imeni fgetc() ali fgets().

PhpToken

Tudi tokenizer in funkcije okoli token_get_all so prenesene na predmete. Tokrat ne gre za to, da bi se znebili virov, temveč dobimo polnopraven objekt, ki predstavlja en PHP-žeton.

<?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() vrne true za žetone T_WHITESPACE, T_COMMENT, T_DOC_COMMENT in T_OPEN_TAG.

Šibki zemljevidi

Šibki zemljevidi so povezani z zbiralnikom smeti, ki iz pomnilnika sprosti vse predmete in vrednosti, ki se ne uporabljajo več (tj. ne vsebuje jih nobena spremenljivka ali lastnost). Ker so niti PHP kratkotrajne in imamo v strežnikih na voljo veliko pomnilnika, se z vprašanji v zvezi z učinkovitim sproščanjem pomnilnika običajno sploh ne ukvarjamo. Vendar so za dlje časa delujoče skripte bistvenega pomena.

Objekt WeakMap je podoben objektu SplObjectStorage. Oba uporabljata predmete kot ključe in omogočata shranjevanje poljubnih vrednosti pod njimi. Razlika je v tem, da objekt WeakMap ne preprečuje, da bi ga zbiralnik smeti sprostil. Tj. če je edino mesto, kjer objekt trenutno obstaja, ključ v slabi mapi, bo objekt odstranjen iz mape in pomnilnika.

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

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

Za kaj je to dobro? Na primer za predpomnjenje. Imejmo metodo loadComments(), ki ji posredujemo blogovski članek in vrne vse njegove komentarje. Ker se metoda večkrat pokliče za isti članek, bomo ustvarili še eno metodo getComments(), ki bo rezultat prve metode poslala v predpomnilnik:

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

	...
}

Gre za to, da se ob sprostitvi objekta $article (na primer aplikacija začne delati z drugim člankom) iz predpomnilnika sprosti tudi njegov vnos.

PHP JIT (Just in Time Compiler)

Morda veste, da je PHP sestavljen v t. i. opcode, ki so navodila nizke ravni, ki jih lahko vidite na primer tukaj in ki jih izvaja virtualni stroj PHP. In kaj je JIT? JIT lahko PHP pregledno sestavi neposredno v strojno kodo, ki jo neposredno izvaja procesor, tako da je počasnejše izvajanje s strani navideznega stroja zaobideno.

Namen JIT je torej pospešiti PHP.

Prizadevanja za implementacijo JIT v PHP segajo v leto 2011, za njimi pa stoji Dmitrij Stogov. Od takrat je preizkusil tri različne izvedbe, vendar nobena od njih ni prišla v končno izdajo PHP iz treh razlogov: rezultat nikoli ni bil znatno povečanje zmogljivosti za tipične spletne aplikacije; otežuje vzdrževanje PHP (tj. nihče razen Dmitrija je ne razume 😉); obstajali so drugi načini za izboljšanje zmogljivosti brez uporabe JIT.

Skokovito povečanje zmogljivosti, opaženo v različici PHP 7, je bil stranski produkt dela na JIT, čeprav paradoksalno ni bil uporabljen. To se dogaja šele zdaj v PHP 8. Vendar bom zadržal pretirana pričakovanja: verjetno ne boste videli nobenega povečanja hitrosti.

Zakaj torej JIT vstopa v PHP? Prvič, drugih načinov za izboljšanje zmogljivosti počasi zmanjkuje, JIT pa je preprosto naslednji korak. V običajnih spletnih aplikacijah ne prinaša nobenih izboljšav hitrosti, vendar znatno pospeši na primer matematične izračune. To odpira možnost, da te stvari začnemo pisati v jeziku PHP. Pravzaprav bi bilo mogoče nekatere funkcije, ki so prej zaradi hitrosti zahtevale neposredno implementacijo v jeziku PHP, implementirati neposredno v jeziku C.

JIT je del razširitve opcache in je omogočen skupaj z njo v php.ini (preberite dokumentacijo o teh štirih številkah):

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

Da je JIT zagnan, lahko preverite na primer na informacijski plošči Tracy Bar.

JIT deluje zelo dobro, če imajo vse spremenljivke jasno določene tipe in se ne morejo spremeniti niti ob večkratnem klicanju iste kode. Zato se sprašujem, ali bomo nekega dne tudi v PHP za spremenljivke deklarirali tipe: string $s = 'Bye, this is the end of the series';