У листопаді минулого року вийшла PHP 8.3. Чергова версія містить низку вдосконалень - підвищилася продуктивність і покращилася сумісність типів. Також було додано нові функції та виправлено наявні помилки. Розглянемо деякі з найважливіших змін, що підвищили якість релізу.
Система типів
Цей аспект є найбільш вразливим місцем в PHP і тому з кожним релізом відбувається поступове поліпшення простору сумісності типів мови. У новій версії запровадили можливість типізації констант класів і вдосконалили механізм отримання констант класу та об'єктів перерахувань Enum.
Типізація констант класів
У попередніх версіях мови тип і значення оголошеної всередині класу константи могли бути докорінно змінені в дочірніх класах та інтерфейсах, що вносило плутанину в код і могло стати джерелом серйозної помилки. Нижче наведено приклади коду, написаного для попередніх версій мови:
class Testing_oldversion {
const string NEW_CONSTANT = 'oldversion';
}
class Testing_oldversion {
const string NEW_CONSTANT = [];
}
Як видно, тут константа NEW_CONSTANT не тільки "втратила" своє первісне значення, а й була перевизначена з рядкового типу в масив. При цьому інтерпретатор мови не припинив роботи і продовжив виконання коду.
Починаючи з версії PHP 8.3 такі перевизначення стали неможливими, оскільки константи класів стали типізованими. У разі перевизначення типу константи в дочірніх класах або визначеннях інтерфейсів парсер інтерпретатора мови видає фатальну помилку (Fatal error) і виконання коду припиняється. Це вносить у код порядок і передбачуваність, а також є ще одним кроком до вдосконалення системи типізації в PHP.
Оголосити тип константи всередині класу тепер можна відразу після ключового слова const, як показано нижче:
class Testing_newversion {
const string NEW_CONSTANT = 'newversion';
}
class Testing_newversion {
const string NEW_CONSTANT = [];
}
// Fatal error: Cannot use array as value for class constant
// Testing_newversion::PHP of type string
Як видно, парсер видав повідомлення про фатальну помилку (Не можу перевизначити в масив константу класу) і припинив виконання коду. Переваги нового підходу очевидні.
Динамічне отримання констант класів і об'єктів Enum
У попередніх версіях неможливо було витягувати константи та об'єкти Enum з ім'ям змінної в динамічному режимі. Спроба це зробити призводила до синтаксичної помилки, оскільки синтаксична конструкція ClassName::{$varName} не допускалася.
Приклад коду з вилучення об'єкта Enum для попередніх версій:
enum OldEnum: int {
case OldMember = 30;
}
$enumName1 = 'OldMember';
echo OldEnum::{$enumName1}->value;
Відповідь парсера:
Parse error: syntax error, unexpected token "->", expecting "(" in ``` on line ```
Приклад коду з вилучення константи класу для версії PHP 8.3:
class NewClass {
public const NEW_CONST = 35;
}
$constName1 = 'NEW_CONST';
echo NewClass::{$constName1};
Як бачимо, синтаксична конструкція ClassName::{$varName} тепер стала припустимою і тому інтерпретатор не зупиняється, а благополучно витягує константу та продовжує роботу.
Коректне перевизначення методів класу / інтерфейсу
До виходу версії PHP 8.3 можлива була ситуація, коли розробник міг випадково використати ім'я методу, який вже існує в батьківському класі або реалізованому інтерфейсі, що призводило до непередбачуваного перевизначення батьківського методу і, як результат, помилки в коді. Щоб цього уникнути, у новій версії було прийнято атрибут #[\Override], що виключає непередбачене перевизначення батьківського методу.
Перевизначення батьківського методу тепер можливе тільки за наявності зазначеного атрибута перед визначенням нового методу з тим самим ім'ям. У разі, якщо атрибут вказано, а батьківський метод відсутній, то парсер під час перевірки коду видасть фатальну помилку і зупинить виконання програми.
Приклад неправильного використання атрибута:
class OneTest {
#[\Override]
public function newMethod(): void {}
}
Відповідь парсера:
Fatal error: OneTest::newMethod() has #[\Override] attribute, but no matching parent method exists in ... on line ...
Причина помилки - відсутність батьківського методу.
Приклад правильного коду:
class MainClass {
public function newMethod() {}
}
class ChildrenClass extends MainClass {
#[\Override]
public function newMethod() {}
}
Атрибут може використовуватися не тільки для перевизначення методів класу або інтерфейсу, але також і методів перелічень (Enum) і трейтів (Traits). Нижче наведено приклади використання атрибута #[\Override] для зазначених випадків.
interface MainInterface {
public function oneMethod(): void;
}
enum ChildrenEnum implements MainInterface {
#[\Override]
public function oneMethod(): void {}
}
trait MainTrait {
#[\Override]
public function testMethod() {}
}
class NewClass {
use MainTrait;
public function testMethod(): void {}
}
В обох випадках використання атрибута є коректним і допомагає позбутися помилок, що неминуче з'являлися при використанні попередніх версій мови.
Перевірка на відповідність JSON-формату
З'явився покращений варіант функції, що здійснює перевірку на відповідність синтаксису рядка JSON-формату. Стара версія функції - json_decode(). Нова - json_validate().
На відміну від першого варіанта, нова функція не здійснює декодування оброблюваного рядка, що призводить до значного споживання машинних ресурсів, зокрема, процесорного часу і пам'яті. Json_validate() використовує для аналізу базовий аналізатор JSON, що не призводить до отримання декодованого значення рядка і тому значно економить машинні ресурси.
Приклади застосування:
var_dump(json_validate('{ "onetesting": { "less": "moo" } }')); // true
json_validate(""); // false
json_validate("null"); // true
Необхідно зазначити, що нова функція не повертає код помилки перевірки. Для цієї мети можна використовувати функції json_last_error() і json_last_error_msg().
Нові методи класу Randomizer
Клас було розширено двома групами методів, що визначають різні формати генерації випадкових послідовностей чисел модулем Random, який було додано в попередній версії PHP.
Генерація чисел із плаваючою комою
Використання методів getFloat() і nextFloat() дає змогу отримувати на виході випадкові числа з плаваючою комою незсувним чином, що лежать у заданому інтервалі значень. Досі цьому заважали неявне округлення чисел і їхня обмежена точність.
Приклад використання:
$rdm = new \Random\Randomizer();
$allvalue = $rdm->getFloat(
-5.2,
30.7,
\Random\IntervalBoundary::ClosedClosed,
);
$valueTrue = 0.1;
// Randomizer::nextFloat() is equivalent to
// Randomizer::getFloat(0, 1, \Random\IntervalBoundary::ClosedOpen).
// The upper bound, i.e. 1, will not be returned.
$mainBool = $rdm->nextFloat() < $valueTrue;
Генерація довільного числа байтів
Метод getBytesFromString() дає змогу отримувати рядки, що складаються з певної кількості байтів, що необхідно, наприклад, під час отримання числових ідентифікаторів певної довжини.
Приклад використання:
$rdm = new \Random\Randomizer();
$randomMyDomain = sprintf(
"%s.site.ua",
$rdm->getBytesFromString(
'abcdefghijk01234567',
7,
),
);
echo $randomMyDomain;
Цей приклад дає змогу отримувати імена піддоменів для сайту site.ua, які складаються із семи випадково обраних символів із наперед заданого списку.