PHP 8.3 что нового. Изменения и новый функционал.

Источник: «PHP 8.3: new features (with RFCs) and release date»
PHP 8.3 вышел 23 ноября 2023 года, и, как обычно, вам нужно быть в курсе новых функций и критических изменений, чтобы упростить переход.

PHP — проект с открытым исходным кодом. Узнать то, что будет включено в новую версию занимает всего минуту поиска. Например, на этой странице перечислены все принятые RFC для PHP 8.3.

Ниже вы найдёте краткий список нововведений с примерами кода.

Когда выйдет PHP 8.3?

Согласно шестимесячной фазы пре-релизов, PHP 8.3 вышел 23 ноября 2023 года после трёх альфа-релизов, трёх бета-версий и шести релиз-кандидатов.

ДатаРелиз
8 Июня, 2023Первый альфа-релиз
22 Июня, 2023Второй альфа-релиз
6 Июля, 2023Третий альфа-релиз
18 Июля, 2023Заморозка функций
20 Июля, 2023Первый бета-релиз
3 Августа, 2023Второй бета-релиз
17 Августа, 2023Третий бета-релиз
31 Августа, 2023Первый релиз-кандидат
14 Сентября, 2023Второй релиз-кандидат
28 Сентября, 2023Третий релиз-кандидат
12 Октября, 2023Четвёртый релиз-кандидат
26 Октября, 2023Пятый релиз-кандидат
9 Ноября, 2023Шестой релиз-кандидат
23 Ноября, 2023GA

Новые возможности PHP 8.3

json_validate()

Ранее единственным способом проверить, является ли строка валидной JSON строкой, было её декодирование и определение наличия ошибок. Эта новая функция json_validate() удобна, если вам нужно только знать, является полученная JSON строка валидной, поскольку она использует меньше памяти по сравнению с декодированием строки.

json_validate(string $json, int $depth = 512, int $flags = 0): bool

Пример использования json_validate:

json_validate('{ "foo": "bar", }');

// Syntax error
echo json_last_error_msg();

Как видите, json_validate() возвращает логическое значение, и вы можете получить сообщение об ошибке с помощью json_last_error() или json_last_error_msg().

Узнать больше можно в статье PHP 8.3: Добавлена функция json_validate

gc_status возвращает дополнительную информацию

В PHP 8.3 функция gc_status() возвращающая информацию о GC будет возвращать четыре новых поля с дополнительной информацией. В массив с возвращаемой информацией добавлены поля: running, protected, full и buffer_size.

var_dump(gc_status());

Узнать больше можно в статье PHP 8.3: gc_status дополнительная информацию о GC

Изменения в PHP 8.3

Правки Readonly

В этом RFC было предложено два изменения, но приняли только одно: возможность повторной инициализации свойств readonly во время клонирования. Это может показать очень важным, но этот RFC касается только весьма конкретного (не важного) пограничного случая: перезапись значения свойств в __clone(), чтобы разрешить глубокое копирование readonly свойств.

readonly class Post
{
public function __construct(
public DateTime $createdAt,
) {}

public function __clone()
{
$this->createdAt = new DateTime();
// Это разрешено,
// несмотря то, что `createdAt` является readonly свойством.
}
}

Типизированные константы класса

Теперь вы можете указывать тип константы класса:

class Foo
{
const string BAR = 'baz';
}

Атрибут #[Override]

Новый атрибут #[Override] используется для отображения намерений программиста. В основном это говорит: Я знаю, что этот метод переопределяет родительский метод. Если это когда-нибудь изменится, пожалуйста, дайте мне знать.

Пример:

abstract class Parent
{
public function methodWithDefaultImplementation(): int
{
return 1;
}
}

final class Child extends Parent
{
#[Override]
public function methodWithDefaultImplementation(): void
{
return 2; // Переопределённый метод
}
}

Теперь представьте, что в какой-то момент родительский метод меняет имя своего метода:

abstract class Parent
{
public function methodWithNewImplementation(): int
{
return 1;
}
}

Благодаря атрибуту #[Override] PHP сможет определить, что Child::methodWithDefaultImplementation() больше ничего не переопределяет и выдаст ошибку.

Больше об атрибуте #[Override] вы можете прочитать в статье #[Override] в PHP 8.3.

Отрицательные индексы в массивах

Если у вас есть пустой массив, добавьте элемент с отрицательным индексом, а затем ещё один элемент, этот второй элемент всегда будет начинаться с индекса 0:

$array = [];

$array[-5] = 'a';
$array[] = 'b';

var_export($array);

//array (
// -5 => 'a',
// 0 => 'b',
//)

Начиная с PHP 8.3, следующий элемент будет добавлен с индексом -4:

//array (
// -5 => 'a',
// -4 => 'b',
//)

Анонимные readonly классы

Ранее нельзя было пометить анонимные классы, как readonly. Это исправлено в PHP 8.3:

$class = new readonly class {
public function __construct(
public string $foo = 'bar',
) {}
};

unserialize() обновление ошибки E_NOTICE до E_WARNING

До версии PHP 8.3 при передаче недопустимой строки функции unserialize() в некоторых случаях, например при синтаксической ошибке в сериализованной строке выдавалось PHP уведомление (E_NOTICE). Начиная с версии PHP 8.3 и в более поздних версиях это было изменено на выдачу предупреждений (E_WARNING). Кроме того, некоторые условия функции serialize() изменены, чтобы тоже выдавать E_WARNING

unserialize("invalid-string");
- PHP Notice:  unserialize(): Error at offset 0 of 14 bytes
+ PHP Warning: unserialize(): Error at offset 0 of 14 bytes

В PHP 8.0 уровень сообщений об ошибках PHP по умолчанию был изменён на E_ALL. Если значение error_reporting не было изменено в пользовательском INI-файле, это не должно привести к возникновению каких-либо новых ошибок, кроме изменения уровня серьёзности.

Узнать больше можно в статье PHP 8.3: unserialize() обновление ошибки E_NOTICE до E_WARNING

Дополнения Randomizer

В PHP 8.2 добавлен новый класс Randomizer. Это обновление содержит несколько небольших дополнений:

Randomizer::getBytesFromString(string $string, int $length): string

Этот метод позволяет сгенерировать строку заданной длины, состоящую из случайно выбранных байтов из заданной строки.

Randomizer::getFloat(
float $min,
float $max,
IntervalBoundary $boundary = IntervalBoundary::ClosedOpen
): float

getFloat() возвращает число с плавающей точкой между $min и $max. Вы можете определить, следует ли включать $min и $max благодаря перечислению IntervalBoundary. Closed означает, что значение включено, а Open означает, что оно исключено.

Randomizer::nextFloat(): float {}

Наконец, nextFloat() является сокращением для getFloat(0, 1, IntervalBoundary::ClosedOpen), другими словами оно даст вам случайное число с плавающей точкой между 0 и 1, где 1 исключается.

Суммируя:

final class Randomizer {
public function getBytesFromString(string $string, int $length) : string {}
public function nextFloat() : float {}
public function getFloat(float $min, float $max, IntervalBoundary $boundary = IntervalBoundary::ClosedOpen) : float {}
}

enum IntervalBoundary
{
case ClosedOpen;
case ClosedClosed;
case OpenClosed;
case OpenOpen;
}

Список изменений:

  1. Переименован getBytesFromAlphabet в getBytesFromString, добавлено перечисление GetFloatBounds
  2. Переименован GetFloatBounds в IntervalBoundary.
  3. Переименован третий параметр getFloat() с $bounds в $boundary, чтобы он соответствовал именам перечисления.

Узнать подробности можно в статье PHP 8.3: Дополнение Randomizer

Извлечение констант динамического класса

PHP 8.3 позволяет получать константы с более динамическим синтаксисом:

class Foo
{
const BAR = 'bar';
}

$name = 'BAR';

// Вместо этого:
constant(Foo::class . '::' . $name);

// Вы можете сделать так:
Foo::{$name};

Более подходящие исключения Date/Time

Во многих случаях PHP просто генерирует объект Exception или Error, или выдаёт предупреждение или ошибку, когда что-то пошло не так при работе с датами и временем. Этот RFC проходит через все эти крайности и добавляет для них соответствующие специальные исключения.

Теперь у нас есть такие исключения, как DateMalformedIntervalStringException, DateInvalidOperationException и DateRangeError.

Как правило, эти дополнения не нарушают код, поскольку эти недавно добавленные исключения и ошибки являются подклассами общих классов Exception и Error. Однако, в этом RFC есть три небольших критических изменения:

Изменения в функции range()

Из changelog:

Трейты и статические свойства

Из changelog:

Использование трейтов со статическими свойствами теперь будет повторно объявлять статические свойства, унаследованные от родительского класса. Это создаст отдельное хранилище статических свойств для текущего класса. Это аналогично добавлению статического свойства в класс напрямую без трейтов.

Обнаружение переполнения стека

В PHP 8.3 добавлены две новые ini директивы с именами zend.max_allowed_stack_size и zend.reserved_stack_size. Программы, близкие к переполнению стека вызовов, теперь смогут выдавать Error при использовании большего, чем разница между zend.max_allowed_stack_size and zend.reserved_stack_size.

Преимущество этой функции заключается в том, что ошибки сегментации, вызванные переполнением стека, больше не будут приводить к ошибкам сегментации, что значительно упрощает отладку.

Значение по умолчанию для zend.max_allowed_stack_size равно 0, что означает, что PHP автоматически определит значение. Вы также можете указать -1, чтобы указать, что нет ограничения или определённого количества байт. Директива zend.reserved_stack_size используется для определения буферной зоны, так что PHP может по-прежнему выдавать ошибку вместо фактического исчерпания памяти. Её значение должно быть числом байт, но PHP сам определит для вас разумное значение по умолчанию, поэтому не обязательно устанавливать его, если только вы не сталкиваетесь с пограничными случаями для конкретных программ.

В заключение отметим, что для fiber в качестве максимально допустимого размера стека используется существующая директива fiber.stack_size.

zend.max_allowed_stack_size=128K

Новая функция mb_str_pad

Из RFC:

В PHP различные строковые функции доступны в двух вариантах: одна для байтовых строк, другая - для многобайтовых. Однако среди многобайтовых строковых функций заметно отсутствие mbstring-эквивалента функции str_pad(). В функции str_pad() отсутствует поддержка многобайтовых символов, что вызывает проблемы при работе с языками, использующими многобайтовые кодировки, например UTF-8. В данном RFC предлагается добавить такую функцию в PHP, которую мы назовём mb_str_pad().

Функция выглядит следующим образом:

function mb_str_pad(
string $string,
int $length,
string $pad_string = " ",
int $pad_type = STR_PAD_RIGHT,
?string $encoding = null,
): string {}

Магические замыкания методов и именованные аргументы

Допустим, у вас есть класс, поддерживающий магические методы:

class Test {
public function __call($name, $args)
{
var_dump($name, $args);
}

public static function __callStatic($name, $args) {
var_dump($name, $args);
}
}

PHP 8.3 позволяет создавать замыкания из этих методов, а затем передавать именованные аргументы этим замыканиям. Ранее это было невозможно.

$test = new Test();

$closure = $test->magic(...);

$closure(a: 'hello', b: 'world');

Инвариантная видимость констант

Ранее видимость констант не проверялась при реализации интерфейса. В PHP 8.3 эта ошибка исправлена, но в некоторых местах она может привести к поломке кода, если вы не знали о таком поведении.

interface I {
public const FOO = 'foo';
}

class C implements I {
private const FOO = 'foo'; // Ошибка
}

Небольшие устаревания RFC

Как обычно бывает с каждым выпуском, в нем есть один RFC, который добавляет кучу мелких устареваний/deprecations. Следует помнить, что deprecations — это не ошибки, и в целом они являются положительным моментом для развития языка. Вот те deprecations, которые были внесены, более подробно о них можно прочитать в RFC:

Небольшие, но заметные изменения в PHP 8.3

Не каждое изменение в PHP проходит процесс RFC. На самом деле, большинство изменений включают поддержку и исправление ошибок и не требуют RFC. Все эти изменения перечислены в UPGRADING. Я перечислю некоторые из самых известных, но вам обязательно следует прочитать весь список, если хотите знать о мельчайших деталях.

Дополнительные материалы

Предыдущая Статья

Вышел PHP 8.3 с типизированными константами классов, функцией json_validate и многим другим

Следующая Статья

Что на самом деле происходит при выполнении команды 'Docker Run'