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

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

Пусната е версия 8.0 на PHP. Тя е пълна с нови функции, както никоя друга версия досега. Тяхното представяне заслужава четири отделни статии. В последната част ще разгледаме новите функции и класове и ще представим 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 (previously 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, а в случай на обекти връща директно типа:

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)

Миграция на ресурси към обекти

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

От PHP 8.0 ресурсите изображения, curl joins, 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];         // 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

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

Слаби карти

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

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

$map = new WeakMap;
$obj = new stdClass;
$map[$obj]  = 'data for $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 се компилира в т.нар. опкод, който представлява инструкции от ниско ниво, които можете да видите например тук и които се изпълняват от виртуалната машина на PHP. А какво е JIT? JIT може прозрачно да компилира PHP директно в машинен код, който се изпълнява директно от процесора, така че да се заобиколи по-бавното изпълнение от виртуалната машина.

Следователно JIT е предназначен за ускоряване на PHP.

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

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

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

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

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

Можете да проверите дали JIT е стартиран, например в информационния панел Tracy Bar.

JIT работи много добре, ако всички променливи имат ясно определени типове и не могат да се променят дори при многократно извикване на един и същ код. Затова се чудя дали един ден ще декларираме типове и в PHP за променливите: string $s = 'Bye, this is the end of the series';

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