PHP 8.0: Нови функции, класове и JIT (4/4)

преди 4 години от David Grudl  

Излезе PHP версия 8.0. Тя е толкова натъпкана с новости, колкото никоя версия преди нея. Представянето им изисква цели четири отделни статии. В тази последна ще разгледаме новите функции и класове и ще представим Just in Time Compiler.

Нови функции

Стандартната библиотека на PHP разполага със стотици функции и във версия 8.0 се появиха шест нови. Изглежда малко, но повечето от тях запълват слаби места на езика. Което добре кореспондира с излъчването на цялата версия 8.0, която довършва и консолидира PHP както никоя версия преди нея. Преглед на всички нови функции и методи ще намерите в ръководството за миграция.

str_contains() str_starts_with() str_ends_with()

Функции за установяване дали низ започва, завършва или съдържа в себе си подниз.

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

Заедно с появата на тази тройка PHP дефинира как да се работи с празен низ при търсене, според което се ръководят и всички останали свързани функции, и то така, че празният низ се намира навсякъде:

str_contains('Nette', '')     // true
str_starts_with('Nette', '')  // true
strpos('Nette', '')           // 0 (преди false)

Благодарение на това поведението на тройката е напълно идентично с аналозите в Nette:

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

Защо тези функции са толкова важни? Стандартните библиотеки на всички езици винаги са обременени от историческото развитие и не може да се избегне възникването на неконсистентности и грешки. Но същевременно те са визитката на дадения език. Учудващо е, когато при 25-годишен PHP липсват функции за толкова основни операции като връщане на първия или последния елемент от масив, екраниране на HTML без капани (htmlspecialchars не екранира апостроф) или именно търсене на низ в низ. Че това може да се заобиколи някак не издържа, защото резултатът тогава не е четим и разбираем код. Това е поука за всички автори на API. Когато видите, че значителна част от документацията на функцията заема обяснение на капаните (като например върнатите стойности на strpos), това е ясен сигнал да се коригира библиотеката и да се допълни именно str_contains.

get_debug_type()

Замества вече остарялата get_type(). Вместо дълги типове като integer връща днес използваните int, в случай на обекти връща направо типа:

Стойност 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)

Обективизация на ресурсите

Стойностите от тип resource произхождат от времената, когато PHP все още нямаше обекти, но всъщност ги изискваше. Така се появиха ресурсите. Днес имаме обекти и в сравнение с ресурсите те работят много по-добре с garbage collector-а, така че в план е постепенно всички да бъдат заменени с обекти.

От PHP 8.0 се променят на обекти ресурсите за изображения, curl връзки, openssl, xml и др.. В PHP 8.1 ще дойде ред на FTP връзките и т.н.

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

Тези обекти засега нямат никакви методи, нито можете директно да създавате техни инстанции. Засега става дума наистина само за това да се извадят от PHP остарелите ресурси без промяна на API. И това е добре, защото създаването на добро API е отделна и трудна задача. Никой не желае в PHP да възникнат още класове като SplFileObject с методи, наречени fgetc() или fgets().

PhpToken

В обекти се премества и токенизаторът, а следователно и функциите около token_get_all. Този път не става дума за отърваване от ресурси, а получаваме пълноценен обект, представляващ един PHP токен.

<?php

$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0];         // инстанция 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

Методът isIgnorable() връща true за токени T_WHITESPACE, T_COMMENT, T_DOC_COMMENT и T_OPEN_TAG.

Слаби карти (Weak maps)

Слабите карти са свързани с garbage collector-а, който освобождава от паметта всички обекти и стойности, които вече не се използват (т.е. няма използвана променлива или свойство, което да ги съдържа). Тъй като животът на PHP нишката е краткотраен и днес имаме достатъчно памет на сървърите, въпросите около ефективното освобождаване на паметта обикновено изобщо не ги решаваме. Но при по-дълго работещи скриптове те са съществени.

Обектът WeakMap е подобен на SplObjectStorage. И в двата като ключове се използват обекти и позволяват под тях да се съхраняват произволни стойности. Разликата е в това, че WeakMap не пречи обектът да бъде освободен от garbage collector-а. Т.е. ако единственото място, където обектът все още се среща, е ключ в слабата карта, той ще бъде премахнат от картата и паметта.

$map = new WeakMap;
$obj = new stdClass;
$map[$obj]  = 'данни за $obj';

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

За какво е добро това? Например за кеширане. Да имаме метод loadComments(), на който предаваме статия от блог и той връща всички нейни коментари. Тъй като методът се извиква със същата статия многократно, ще си създадем още getComments(), която ще кешира резултата от първия метод:

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

	...
}

Номерът е в това, че в момента, в който се освободи обектът $article (например приложението започне да работи с друга статия), ще се освободи и неговият запис от кеша.

PHP JIT (Just in Time Compiler)

Може би знаете, че PHP се компилира до т.нар. opcode, което са low-level инструкции, които можете да видите например тук и които се изпълняват от виртуалната машина на PHP. А какво е JIT? JIT може прозрачно да компилира PHP директно в машинен код, който се изпълнява директно от процесора, така че се заобикаля по-бавното изпълнение от виртуалната машина.

JIT следователно трябва да ускори PHP.

Усилието за имплементиране на JIT в PHP датира от 2011 г. и зад него стои Дмитрий Стогов. Оттогава е изпробвал 3 различни имплементации, но никоя от тях не е достигнала до официалния PHP и то по следните причини: резултатът никога не е бил съществено повишаване на производителността за типични уеб приложения; усложнява поддръжката на PHP (т.е. никой освен Дмитрий не го разбира 😉); съществували са други начини за подобряване на производителността, без да се налага да се използва JIT.

Скоковото повишаване на производителността на PHP във версия 7 беше страничен продукт от работата по JIT, макар че парадоксално до неговото внедряване не се стигна. Това се случва чак в PHP 8. Но веднага ще охладня прекалените очаквания: вероятно няма да забележите никакво ускорение.

Защо тогава JIT влиза в PHP? Първо, другите пътища за подобряване на производителността бавно се изчерпват и JIT просто е наред. В обикновените уеб приложения той наистина не носи ускорение, но съществено ускорява например математически изчисления. Така се отваря възможност да започнем да пишем тези неща в PHP. И всъщност би било възможно директно в PHP да се имплементират функции, които досега изискваха имплементация директно в C заради скоростта.

JIT е част от разширението opcache и се включва заедно с него в php.ini (прочетете документацията за онази четворка цифри):

zend_extension=php_opcache.dll
opcache.jit=1205              ; конфигурация чрез четворка OTRC
opcache.enable_cli=1          ; за да работи и в CLI
opcache.jit_buffer_size=128M  ; заделена памет за компилиран код

Че JIT работи, ще разберете например в информационния панел в Tracy Bar.

JIT работи много добре тогава, когато всички променливи имат ясно зададени типове и не могат да се променят при повторно извикване на същия код. Затова съм любопитен дали някога в PHP ще декларираме типове и при променливите: string $s = 'Здравей, това е краят на сериала';

Последни публикации