PHP 8.0: Новые функции, классы и JIT (4/4)
Вышла версия 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 еще не было объектов, но они фактически были нужны. Так появились ресурсы. Сегодня у нас есть объекты, и по сравнению с ресурсами они гораздо лучше работают со сборщиком мусора, поэтому в планах постепенно заменить все ресурсы объектами.
С 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
Weak maps связаны со сборщиком мусора, который освобождает из памяти все объекты и значения, которые больше не используются (т.е. нет используемой переменной или свойства, которые бы их содержали). Поскольку жизнь PHP-потока коротка, а памяти на серверах сегодня у нас достаточно, вопросы эффективного освобождения памяти, как правило, мы вообще не решаем. Но для длительно работающих скриптов они являются ключевыми.
Объект WeakMap
похож на
SplObjectStorage
. В обоих в качестве ключей
используются объекты, и они позволяют
хранить под ними любые значения. Разница в
том, что WeakMap
не препятствует тому,
чтобы объект был освобожден сборщиком
мусора. Т.е. если единственным местом, где
объект еще встречается, является ключ в weak
map, он будет удален из карты и памяти.
$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, это низкоуровневые инструкции, которые можно посмотреть, например, здесь и которые выполняет виртуальная машина 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 Baru.
JIT очень хорошо работает тогда, когда все
переменные имеют четко заданные типы и не
могут изменяться при повторном вызове
одного и того же кода. Поэтому мне
интересно, будем ли мы когда-нибудь в PHP
декларировать типы и для
переменных: string $s = 'Привет, это заключение серии';
Чтобы оставить комментарий, пожалуйста, войдите в систему