PHP 8.0: новые функции, классы и JIT (4/4)
Вышла версия 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';
Чтобы оставить комментарий, пожалуйста, войдите в систему