PHP 8.0: Új függvények, osztályok és JIT (4/4)

4 éve írta David Grudl  

Megjelent a PHP 8.0-s verziója. Annyira tele van újdonságokkal, mint még egyetlen verzió sem korábban. Bemutatásukhoz egyenesen négy különálló cikkre volt szükség. Ebben az utolsóban az új függvényeket és osztályokat nézzük meg, és bemutatjuk a Just in Time Compilert.

Új függvények

A PHP standard könyvtára több száz függvényt tartalmaz, és a 8.0-s verzióban hat új jelent meg. Ez kevésnek tűnik, de többségük a nyelv gyenge pontjait pótolja. Ami szépen illeszkedik az egész 8.0-s verzió hangulatához, amely úgy tökéletesíti és konszolidálja a PHP-t, mint még egyetlen verzió sem korábban. Az összes új függvény és metódus áttekintését a migration guide-ban találja.

str_contains() str_starts_with() str_ends_with()

Függvények annak megállapítására, hogy egy string kezdődik-e, végződik-e, vagy tartalmaz-e egy részstringet.

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

Ezzel a hármassal együtt a PHP definiálja, hogyan kell kezelni az üres stringet a keresés során, amihez az összes többi kapcsolódó függvény is igazodik, mégpedig úgy, hogy az üres string mindenhol megtalálható:

str_contains('Nette', '')     // true
str_starts_with('Nette', '')  // true
strpos('Nette', '')           // 0 (korábban false)

Ennek köszönhetően a hármas viselkedése teljesen azonos a Nette-beli megfelelőivel:

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

Miért olyan fontosak ezek a függvények? Minden nyelv standard könyvtára mindig terhelt a történelmi fejlődéssel, és nem lehet elkerülni a következetlenségek és melléfogások kialakulását. De ugyanakkor ez az adott nyelv névjegye is. Meglepő, ha egy 25 éves PHP-ból hiányoznak a függvények olyan alapvető műveletekhez, mint a tömb első vagy utolsó elemének visszaadása, a HTML escapelése trükkök nélkül (htmlspecialchars nem escapeli az aposztrófot), vagy éppen a string keresése stringben. Az, hogy valahogy meg lehet kerülni, nem állja meg a helyét, mert az eredmény nem olvasható és érthető kód lesz. Ez tanulság minden API szerző számára. Ha látja, hogy a függvény dokumentációjának jelentős részét a buktatók magyarázata foglalja el (mint például a strpos visszatérési értékei), az egyértelmű jelzés a könyvtár módosítására és éppen a str_contains kiegészítésére.

get_debug_type()

Helyettesíti a már elavult get_type()-ot. Hosszú típusok helyett, mint az integer, a ma használt int-et adja vissza, objektumok esetén pedig egyenesen a típust:

Érték 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)

Erőforrások objektumosítása

A resource típusú értékek abból az időből származnak, amikor a PHP-nak még nem voltak objektumai, de valójában szüksége volt rájuk. Így jöttek létre az erőforrások. Ma már vannak objektumaink, és az erőforrásokkal szemben sokkal jobban működnek a garbage collectorral, így a terv az, hogy fokozatosan mindet objektumokra cseréljük.

A PHP 8.0-tól kezdve objektumokká válnak a képek, curl kapcsolatok, openssl, xml stb. erőforrásai. A PHP 8.1-ben az FTP kapcsolatok stb. következnek.

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

Ezeknek az objektumoknak még nincsenek metódusaik, és közvetlenül sem hozhat létre példányokat belőlük. Egyelőre valóban csak arról van szó, hogy megszabaduljunk a PHP elavult erőforrásaitól az API megváltoztatása nélkül. És ez jó, mert egy jó API létrehozása önálló és nehéz feladat. Senki sem szeretné, ha a PHP-ban további olyan osztályok jönnének létre, mint az SplFileObject, fgetc() vagy fgets() nevű metódusokkal.

PhpToken

Objektumokba kerül át a tokenizáló is, tehát a token_get_all körüli függvények. Ezúttal nem az erőforrásoktól való megszabadulásról van szó, hanem egy teljes értékű objektumot kapunk, amely egy PHP tokent reprezentál.

<?php

$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0];         // PhpToken példány

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

Az isIgnorable() metódus true-t ad vissza a T_WHITESPACE, T_COMMENT, T_DOC_COMMENT és T_OPEN_TAG tokenekre.

Weak map-ek

A weak map-ek a garbage collectorhoz kapcsolódnak, amely felszabadítja a memóriából az összes olyan objektumot és értéket, amelyeket már nem használnak (azaz nincs olyan használt változó vagy property, amely tartalmazná őket). Mivel a PHP szál élete kérészéletű, és ma már elegendő memória áll rendelkezésünkre a szervereken, a hatékony memóriafelszabadítás kérdéseivel általában egyáltalán nem foglalkozunk. De a hosszabb ideig futó szkripteknél alapvetőek.

Az WeakMap objektum hasonló az SplObjectStorage-hoz. Mindkettőben objektumokat használnak kulcsként, és lehetővé teszik tetszőleges értékek tárolását alattuk. A különbség az, hogy a WeakMap nem akadályozza meg, hogy az objektumot a garbage collector felszabadítsa. Azaz, ha az egyetlen hely, ahol az objektum még előfordul, a weak map kulcsa, akkor eltávolításra kerül a map-ből és a memóriából is.

$map = new WeakMap;
$obj = new stdClass;
$map[$obj]  = 'adatok $obj-hoz';

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

Mire jó ez? Például cache-elésre. Legyen egy loadComments() metódusunk, amelynek átadunk egy blogcikket, és visszaadja annak összes kommentjét. Mivel a metódus ugyanazzal a cikkel ismételten meghívódik, létrehozunk még egy getComments()-t, amely az első metódus eredményét cache-eli:

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

	...
}

A trükk az, hogy abban a pillanatban, amikor az $article objektum felszabadul (például az alkalmazás elkezd egy másik cikkel dolgozni), annak bejegyzése is felszabadul a cache-ből.

PHP JIT (Just in Time Compiler)

Talán tudja, hogy a PHP ún. opcode-ra fordítódik, ami low-level utasítások, amelyeket megnézhet például itt, és amelyeket a PHP virtuális gépe hajt végre. És mi a JIT? A JIT képes a PHP-t transzparensen közvetlenül gépi kódra fordítani, amelyet közvetlenül a processzor hajt végre, így megkerüli a virtuális gép lassabb végrehajtását.

A JIT tehát felgyorsítja a PHP-t.

A JIT PHP-ba való implementálására irányuló törekvés egészen 2011-ig nyúlik vissza, és Dmitry Stogov áll mögötte. Azóta 3 különböző implementációt próbált ki, de egyik sem került be az éles PHP-ba, és ennek okai a következők: az eredmény soha nem volt jelentős teljesítménynövekedés a tipikus webalkalmazásoknál; bonyolítja a PHP karbantartását (azaz senki sem érti Dmitry-n kívül 😉); voltak más utak a teljesítmény javítására anélkül, hogy JIT-et kellett volna használni.

A PHP teljesítményének ugrásszerű növekedése a 7-es verzióban a JIT-en végzett munka mellékterméke volt, bár paradox módon nem került bevezetésre. Ez csak a PHP 8-ban történik meg. De rögtön visszafogom a túlzott elvárásokat: valószínűleg semmilyen gyorsulást nem fog észrevenni.

Miért lép tehát be a JIT a PHP-ba? Egyrészt a teljesítmény javításának más útjai lassan elfogynak, és a JIT egyszerűen sorra kerül. A szokásos webalkalmazásokban ugyan nem hoz gyorsulást, de alapvetően felgyorsítja például a matematikai számításokat. Így lehetőség nyílik arra, hogy ezeket a dolgokat PHP-ban kezdjük el írni. És valójában így közvetlenül PHP-ban lehetne implementálni olyan függvényeket, amelyek eddig a sebesség miatt közvetlenül C-ben történő implementációt igényeltek.

A JIT az opcache kiterjesztés része, és vele együtt kapcsolható be a php.ini-ben (olvassa el a dokumentációt ahhoz a négy számjegyhez):

zend_extension=php_opcache.dll
opcache.jit=1205              ; konfiguráció a négy OTRC számjeggyel
opcache.enable_cli=1          ; hogy CLI-ben is működjön
opcache.jit_buffer_size=128M  ; lefoglalt memória a lefordított kódhoz

Hogy a JIT fut, azt például a Tracy Bar információs paneljén tudhatja meg.

A JIT nagyon jól működik akkor, ha minden változónak világosan meghatározott típusa van, és nem változhatnak meg ugyanazon kód ismételt hívásakor. Kíváncsi vagyok tehát, hogy egyszer a PHP-ban a változóknál is deklarálunk-e majd típusokat: string $s = 'Sziasztok, ez a sorozat vége';

David Grudl Founder of Uměligence and creator of Nette Framework, the popular PHP framework. Since 2021, he's been fully immersed in artificial intelligence, teaching practical AI applications. He discusses weekly tech developments on Tech Guys with his co-hosts and writes for phpFashion and La Trine. He believes AI isn't science fiction—it's a practical tool for improving life today.