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
.
Слабкі мапи
Слабкі мапи пов'язані зі збирачем сміття, який звільняє з пам'яті всі об'єкти та значення, які більше не використовуються (тобто немає жодної використовуваної змінної чи властивості, яка б їх містила). Оскільки життя PHP потоку ефемерне, а пам'яті сьогодні на серверах у нас достатньо, питання щодо ефективного звільнення пам'яті зазвичай ми взагалі не розглядаємо. Але для скриптів, що працюють тривалий час, вони є ключовими.
Об'єкт WeakMap
подібний до
SplObjectStorage
. В обох як ключі
використовуються об'єкти, і вони дозволяють
зберігати під ними будь-які значення.
Різниця в тому, що WeakMap
не заважає
об'єкту бути звільненим збирачем сміття.
Тобто, якщо єдиним місцем, де ще
зустрічається об'єкт, є ключ у слабкій мапі,
він буде видалений з мапи та пам'яті.
$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 Bar.
JIT дуже добре працює тоді, коли всі змінні
мають чітко визначені типи і не можуть
змінюватися при повторному виклику того ж
коду. Тому мені цікаво, чи колись ми будемо в
PHP оголошувати типи також для
змінних: string $s = 'Привіт, це завершення серіалу';
Щоб залишити коментар, будь ласка, увійдіть до системи