В ноябре прошлого года вышла 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, состоящие из семи случайно выбранных символов из наперёд заданного списка.