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

4 года назад от David Grudl  

Вышла версия PHP 8.0. Она полна новых функций, как ни одна другая версия до этого. Их введение заслуживает четырех отдельных статей. В последней части мы рассмотрим новые функции и классы, а также представим компилятор Just in Time.

Новые функции

Стандартная библиотека 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 еще не было объектов, но они были очень нужны. Так родились ресурсы. Сегодня у нас есть объекты, и, по сравнению с ресурсами, они гораздо лучше работают со сборщиком мусора, поэтому планируется постепенно заменить их все на объекты.

Начиная с 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];         // 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.

Скачкообразное увеличение производительности, наблюдаемое в PHP версии 7, было побочным продуктом работы над 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';

Последние сообщения