PHP 8.0: Új funkciók, osztályok és JIT (4/4)

3 éve A címről David Grudl  

Megjelent a PHP 8.0 verziója. Tele van olyan új funkciókkal, mint egyetlen korábbi verzió sem. Bemutatásuk négy külön cikket érdemelt ki. Az utolsó részben az új függvényeket és osztályokat vesszük szemügyre, valamint bemutatjuk a Just in Time Compilert.

Új funkciók

A szabványos PHP könyvtár több száz függvényt tartalmaz, és a 8.0-ás verzióban hat új függvény jelent meg. Ez nem tűnik soknak, de a legtöbbjük a nyelv gyenge pontjait orvosolja. Ami szépen illeszkedik az egész 8.0-s verzió koncepciójához, amely úgy szigorítja és egységesíti a PHP-t, mint egyetlen korábbi verzió sem. Az összes új függvény és metódus áttekintése megtalálható a migrációs útmutatóban.

str_contains() str_starts_with() str_ends_with()

Funkciók annak meghatározására, hogy egy karakterlánc kezdődik-e, végződik-e, vagy tartalmaz-e részláncot.

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

Ennek a hármasságnak a megjelenésével a PHP meghatározza, hogyan kezelje az üres karakterláncot keresés közben, amihez az összes többi kapcsolódó függvény is ragaszkodik, vagyis az üres karakterlánc mindenhol megtalálható:

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

Ennek köszönhetően a hármasság viselkedése teljesen megegyezik a Nette analógokkal:

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 funkciók? Minden nyelv szabványkönyvtárát mindig terheli a történelmi fejlődés; a következetlenségeket és a tévedéseket nem lehet elkerülni. De ugyanakkor ez az adott nyelv tanúbizonysága is. Meglepő módon a 25 éves PHP-ből hiányoznak az olyan alapvető műveletekhez szükséges függvények, mint például egy tömb első vagy utolsó elemének visszaadása, a HTML kódok csúnya meglepetések nélküli eszkábálása (htmlspecialchars nem menekül az aposztróf elől), vagy egyszerűen csak egy karakterlánc keresése egy karakterláncban. Nem állja meg a helyét, hogy ezt valahogyan meg lehet kerülni, mert az eredmény nem olvasható és érthető kód. Ez egy lecke az összes API szerzőnek. Ha azt látod, hogy a függvény dokumentációjának nagy részét a buktatók magyarázatai foglalják el (mint például a strpos visszatérési értékei), az egyértelmű jel arra, hogy módosítsuk a könyvtárat, és adjuk hozzá a str_contains címet.

get_debug_type()

A mára elavult get_type() helyébe lép. A hosszú típusok helyett, mint a integer, a ma használt int, objektumok esetén közvetlenül a típust adja vissza:

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)

Erőforrás-objektum migráció

Az erőforrás-típusú értékek még abból az időből származnak, amikor a PHP még nem rendelkezett objektumokkal, de valójában szükség volt rájuk. Így születtek meg az erőforrások. Ma már vannak objektumok, és az erőforrásokhoz képest sokkal jobban működnek a szemétgyűjtővel, ezért a terv az, hogy fokozatosan az összeset objektumokra cseréljük.

A PHP 8.0-tól a képek, curl joins, openssl, xml, stb. erőforrások objektumokra változnak. A PHP 8.1-ben az FTP-kapcsolatok stb. is követni fogják.

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

Ezeknek az objektumoknak még nincsenek metódusaik, és nem is lehet őket közvetlenül példányosítani. Egyelőre tényleg csak arról van szó, hogy megszabadulunk a PHP elavult erőforrásaitól anélkül, hogy az API-t megváltoztatnánk. És ez jó, mert egy jó API létrehozása egy különálló és nagy kihívást jelentő feladat. Senki sem kívánja új PHP-osztályok létrehozását, mint például a SplFileObject a fgetc() vagy a fgets() nevű metódusokkal.

PhpToken

A tokenizáló és a token_get_all körüli függvények szintén objektumokba kerültek át. Ezúttal nem arról van szó, hogy megszabadulunk az erőforrásoktól, hanem egy teljes értékű objektumot kapunk, amely egy PHP tokent reprezentál.

<?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

A isIgnorable() metódus igazat ad vissza a T_WHITESPACE, T_COMMENT, T_DOC_COMMENT és T_OPEN_TAG tokenekre.

Gyenge térképek

A gyenge leképezések a szemétgyűjtőhöz kapcsolódnak, amely minden olyan objektumot és értéket felszabadít a memóriából, amelyet már nem használnak (azaz nincs olyan változó vagy tulajdonság, amely tartalmazza őket). Mivel a PHP szálak rövid életűek, és a szervereinken rengeteg memória áll rendelkezésre, általában egyáltalán nem foglalkozunk a hatékony memóriafelszabadítással kapcsolatos kérdésekkel. A hosszabb futású szkriptek esetében azonban elengedhetetlenek.

A WeakMap objektum hasonló a SplObjectStorage objektumhoz Mindkettő objektumokat használ kulcsként, és lehetővé teszi 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 szemétgyűjtő felszabadítsa. Vagyis ha az egyetlen hely, ahol az objektum jelenleg létezik, egy kulcs a gyenge térképben, akkor eltávolítja a térképből és a memóriából.

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

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

Mire jó ez? Például gyorsítótárazásra. Legyen egy loadComments() metódusunk, aminek átadunk egy blogcikket, és az visszaadja az összes kommentjét. Mivel a metódust többször is meghívjuk ugyanarra a cikkre, létrehozunk egy másik getComments(), amely az első metódus eredményét gyorsítótárazza:

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 lényeg az, hogy amikor a $article objektumot felszabadítjuk (például az alkalmazás egy másik cikkel kezd el dolgozni), annak bejegyzése is kikerül a gyorsítótárból.

PHP JIT (Just in Time Compiler)

Talán tudod, hogy a PHP-t úgynevezett opcode-okba fordítják, amelyek olyan alacsony szintű utasítások, amelyeket például itt láthatsz, és amelyeket a PHP virtuális gép hajt végre. És mi az a JIT? A JIT transzparens módon képes a PHP-t közvetlenül gépi kóddá fordítani, amelyet közvetlenül a processzor hajt végre, így a virtuális gép általi lassabb végrehajtás megkerülhető.

A JIT célja tehát a PHP felgyorsítása.

A JIT PHP-be való implementálására irányuló törekvés 2011-re 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 a PHP végleges kiadásába, három okból: az eredmény soha nem jelentett jelentős teljesítménynövekedést tipikus webes alkalmazások esetében; bonyolítja a PHP karbantartását (azaz Dmitryn kívül senki sem érti 😉 ); voltak más módszerek is a teljesítmény növelésére JIT használata nélkül.

A 7-es PHP-verzióban megfigyelt ugrásszerű teljesítménynövekedés a JIT-tel kapcsolatos munka mellékterméke volt, bár paradox módon nem került bevetésre. Ez csak most történik meg a PHP 8-ban. De visszafogom a túlzott várakozásokat: valószínűleg nem fogsz sebességnövekedést látni.

Miért kerül be a JIT a PHP-ba? Először is, a teljesítmény javításának más módjai lassan kifogynak, és a JIT egyszerűen a következő lépés. A hétköznapi webes alkalmazásokban nem hoz sebességjavulást, de jelentősen felgyorsítja például a matematikai számításokat. Ez megnyitja a lehetőséget, hogy ezeket a dolgokat PHP-ben kezdjük el írni. Valójában lehetséges lenne néhány olyan függvényt közvetlenül PHP-ben megvalósítani, amelyekhez korábban a sebesség miatt közvetlen C implementációra volt szükség.

A JIT a opcache bővítmény része, és azzal együtt engedélyezhető a php.ini-ben (olvassuk el a dokumentációt arról a négy számjegyről):

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

A JIT futását például a Tracy Bar információs panelen ellenőrizheted.

A JIT nagyon jól működik, ha minden változónak egyértelműen meghatározott típusa van, és nem változhat még akkor sem, ha ugyanazt a kódot ismételten meghívja. Ezért elgondolkodom azon, hogy vajon egyszer majd a PHP-ben is típusokat fogunk-e deklarálni a változókhoz: string $s = 'Bye, this is the end of the series';