PHP 8.0: Повний огляд новинок (1/4)
Вийшла версія PHP 8.0. Вона настільки насичена новинками, як жодна версія до неї. Їх представлення вимагало аж чотирьох окремих статей. У цій першій ми розглянемо, що нового вона приносить з точки зору мови.

Перш ніж зануритися в PHP, знайте, що поточна версія Nette повністю готова до вісімки. Більше того, як подарунок вийшла навіть Nette 2.4, яка повністю сумісна з нею, тому з точки зору фреймворку вам ніщо не заважає почати використовувати нову версію.
Іменовані аргументи
І почнемо одразу з бомби, яку сміливо можна назвати game changer. Тепер можна передавати аргументи функціям і методам не тільки позиційно, але й за іменами. Що абсолютно чудово у випадку, коли метод має дійсно багато параметрів:
class Response implements IResponse
{
public function setCookie(
string $name,
string $value,
string|DateInterface|null $time,
string $path = null,
string $domain = null,
bool $secure = null,
bool $httpOnly = null,
string $sameSite = null
) {
...
}
}
Перші два аргументи передамо позиційно, наступні за іменем: (іменовані завжди повинні йти після позиційних)
$response->setCookie('lang', $lang, sameSite: 'None');
// замість божевільного
$response->setCookie('lang', $lang, null, null, null, null, null, 'None');
До появи цієї функції планувалося
створити в Nette нове API для надсилання cookies,
оскільки кількість параметрів
setCookie()
дійсно зросла, а позиційний
запис був незрозумілим. Тепер це вже не
потрібно, оскільки іменовані аргументи в
цьому випадку є найпрактичнішим API. IDE їх
підказуватимуть і вони мають
перевірку типів.
Чудово підходять і для пояснення логічних
параметрів, де їх використання хоч і не є
необхідним, але саме true
або
false
мало що говорить:
// раніше
$db = $container->getService(Database::class, true);
// тепер
$db = $container->getService(Database::class, need: true);
Імена параметрів тепер стають частиною публічного API. Неможливо їх довільно змінювати, як раніше. З цієї причини Nette також проходить аудит, чи всі параметри мають відповідні імена.
Іменовані аргументи можна використовувати також у комбінації з variadics:
function variadics($a, ...$args) {
dump($args);
}
variadics(a: 1, b: 2, c: 3);
// в $args буде ['b' => 2, 'c' => 3]
Тепер масив $args
може містити і
ненумеровані ключі, що є певним BC break. Те
саме стосується і поведінки функції
call_user_func_array($func, $args)
, де тепер ключі в
масиві $args
відіграють значну роль.
Натомість функції з родини func_*()
відгороджені від іменованих аргументів.
З іменованими аргументами також тісно
пов'язаний факт, що оператор розпакування
...
тепер може розпаковувати і
асоціативні масиви:
variadics(...['b' => 2, 'c' => 3]);
Дивно, але це поки що не працює всередині масивів:
$arr = [ ...['a' => 1, 'b' => 2] ];
// Fatal error: Cannot unpack array with string keys
Комбінація іменованих аргументів та variadics
дає можливість нарешті мати фіксований
синтаксис, наприклад, для методу presenter'а
link()
, якому тепер можемо передавати
іменовані аргументи так само, як і
позиційні:
// раніше
$presenter->link('Product:detail', $id, 1, 2);
$presenter->link('Product:detail', [$id, 'page' => 1]); // мав бути масив
// тепер
$presenter->link('Product:detail', $id, page: 1);
Синтаксис іменованих аргументів набагато
сексуальніший, ніж написання масивів, тому
“Latte негайно взяла його:https://blog.nette.org/…ot-for-least#…
на озброєння”, де його можна
використовувати, наприклад, у тегах
{include}
та {link}
:
{include 'file.latte' arg1: 1, arg2: 2}
{link default page: 1}
До іменованих параметрів ми ще повернемося в третій частині у зв'язку з атрибутами.
Вираз може викинути виняток
Викидання винятку тепер є виразом. Ви
можете, наприклад, обернути його дужками та
додати до умови if
. Гмм, це не звучить
дуже практично. Але ось це вже цікавіше:
// раніше
if (!isset($arr['value'])) {
throw new \InvalidArgumentException('значення не встановлено');
}
$value = $arr['value'];
// тепер, коли throw є виразом
$value = $arr['value'] ?? throw new \InvalidArgumentException('значення не встановлено');
Оскільки стрілочні функції досі можуть містити лише один вираз, завдяки цій функції вони можуть викидати винятки:
// лише один вираз
$fn = fn() => throw new \Exception('ой');
Вирази Match
Конструкція switch-case
має два великі
недоліки:
- використовує нестроге порівняння
==
замість===
- потрібно бути уважним, щоб випадково не
забути
break
Тому PHP пропонує альтернативу у вигляді
нової конструкції match
, яка
використовує строге порівняння і, навпаки,
не використовує break
.
Приклад коду switch
:
switch ($statusCode) {
case 200:
case 300:
$message = $this->formatMessage('ok');
break;
case 400:
$message = $this->formatMessage('не знайдено');
break;
case 500:
$message = $this->formatMessage('помилка сервера');
break;
default:
$message = 'невідомий код стану';
break;
}
І те саме (тільки зі строгим порівнянням),
записане за допомогою match
:
$message = match ($statusCode) {
200, 300 => $this->formatMessage('ok'),
400 => $this->formatMessage('не знайдено'),
500 => $this->formatMessage('помилка сервера'),
default => 'невідомий код стану',
};
Зверніть увагу, що match
не є керуючою
структурою, як switch
, а виразом. Його
кінцеве значення в прикладі ми присвоюємо
змінній. Водночас і окремі “варіанти” є
виразами, тому не можна записати кілька
кроків, як у випадку switch
.
Якщо не відбудеться збігу з жодним із
варіантів (і не існує клаузули default),
викидається виняток UnhandledMatchError
.
До речі, в Latte також існують теги
{switch}
, {case}
та {default}
. Їхня
робота точно відповідає новому match
.
Вони використовують строге порівняння, не
вимагають break
, а в case
можна
вказати кілька значень, розділених
комами.
Оператор Nullsafe
Необов'язкове ланцюгування (optional chaining)
дозволяє писати вираз, обчислення якого
зупиняється, якщо він натрапляє на null. І це
завдяки новому оператору ?->
. Він
замінить багато коду, який би інакше
повторно перевіряв null:
$user?->getAddress()?->street
// означає приблизно
$user !== null && $user->getAddress() !== null
? $user->getAddress()->street
: null
Чому “означає приблизно”? Тому що
насправді вираз обчислюється більш
витончено, і жоден крок не повторюється.
Наприклад, $user->getAddress()
викликається
лише один раз, тому не може виникнути
проблема, спричинена тим, що метод вперше і
вдруге повернув би щось інше.
Цю свіжу фішку рік тому Latte. Тепер вона потрапляє до самого PHP. Чудово.
Просування властивостей конструктора
Синтаксичний цукор, який заощадить подвійне написання типу та чотириразове написання змінної. Шкода тільки, що він не з'явився в той час, коли ми не мали таких розумних IDE, які сьогодні пишуть це за нас 🙂
class Facade
{
private Nette\Database\Connection $db;
private Nette\Mail\Mailer $mailer;
public function __construct(Nette\Database\Connection $db, Nette\Mail\Mailer $mailer)
{
$this->db = $db;
$this->mailer = $mailer;
}
}
class Facade
{
public function __construct(
private Nette\Database\Connection $db,
private Nette\Mail\Mailer $mailer,
) {}
}
З Nette DI працює, можете одразу починати використовувати.
Строга поведінка арифметичних і бітових операторів
Те, що колись вивело динамічні скриптові мови на вершину, з часом стало їхнім найслабшим місцем. PHP колись позбувався “magic quotes”, реєстрації глобальних змінних, а тепер вільну поведінку замінює строгість. Часи, коли в PHP можна було додавати, множити тощо майже будь-які типи даних, для яких це не мало жодного сенсу, давно минули. Починаючи з версії 7.0, PHP стає все більш строгим, а з версії 8.0 спроба використання будь-якого арифметичного/бітового оператора для масивів, об'єктів чи ресурсів закінчується TypeError. Винятком є додавання масивів.
// арифметичні та бітові оператори
+, -, *, /, **, %, <<, >>, &, |, ^, ~, ++, --:
Розумніше порівняння рядків і чисел
Або ж make loose operator great again.
Здавалося б, для оператора нестрогого
порівняння ==
вже немає місця, що це
лише помилка при написанні ===
, але ця
зміна повертає його знову на карту. Якщо вже
він у нас є, нехай поводиться розумно.
Наслідком попереднього “нерозумного”
порівняння була, наприклад, поведінка
in_array()
, яка могла вас неприємно
підвести:
$validValues = ['foo', 'bar', 'baz'];
$value = 0;
dump(in_array($value, $validValues));
// дивно, але повертало true
// з PHP 8.0 повертає false
Зміна в поведінці ==
стосується
порівняння чисел та “числових” рядків і
показана в наступній таблиці:
Порівняння | Раніше | PHP 8.0 |
---|---|---|
0 == "0" |
true | true |
0 == "0.0" |
true | true |
0 == "foo" |
true | false |
0 == "" |
true | false |
42 == " 42" |
true | true |
42 == "42 " |
true | true |
42 == "42foo" |
true | false |
42 == "abc42" |
false | false |
"42" == " 42" |
true | true |
"42" == "42 " |
false | true |
Дивно, що це BC break в самій основі мови, який був схвалений без будь-якого опору. І це добре. Тут JavaScript міг би сильно позаздрити.
Повідомлення про помилки
Багато внутрішніх функцій тепер
викликають TypeError та ValueError замість
попереджень, які можна було легко
пропустити. Було перекласифіковано ряд
попереджень ядра. Оператор приглушення
@
тепер не приглушує фатальні
помилки. А PDO в стандартному режимі викидає
винятки.
Ці речі Nette завжди намагався якось вирішити. Tracy змінювала поведінку оператора приглушення, Database перемикала поведінку PDO, Utils містить заміни стандартних функцій, які викидають винятки замість непомітних попереджень тощо. Приємно бачити, що строгий напрямок, який Nette має у своїй ДНК, стає нативним напрямком мови.
Масиви з від'ємним індексом
$arr[-5] = 'перший';
$arr[] = 'другий';
Яким буде ключ другого елемента? Раніше це
був 0
, з PHP 8 це -4
.
Кінцева кома
Останнє місце, де не могла бути кінцева кома, було визначення параметрів функції. Це вже минуле:
public function __construct(
Nette\Database\Connection $db,
Nette\Mail\Mailer $mailer, // кінцева кома
) {
....
}
$object::class
Магічна константа ::class
працює і з
об'єктами $object::class
, чим повністю
замінює функцію get_class()
.
catch без змінної
І нарешті: у клаузі catch не обов'язково вказувати змінну для винятку:
try {
$container->getService(Database::class);
} catch (MissingServiceException) { // немає $e
$logger->log('....');
}
У наступних частинах нас чекають суттєві новинки в типах даних, ми покажемо, що таке атрибути, які нові функції та класи з'явилися в PHP, і представимо Just in Time Compiler.
Щоб залишити коментар, будь ласка, увійдіть до системи