Новые функции и улучшения в PHP 8.3: полный технический справочник
PHP 8.3 — это значительный минорный релиз языка PHP, который включает существенные улучшения JIT-компилятора, системы типов, readonly-свойств и основных функций для работы с массивами и строками. Выпущенный 23 ноября 2023 года, он вводит типизированные константы классов, уточнения json_validate(), array_is_list(), дополнения Randomizer и глубокое клонирование readonly-свойств — изменения, которые напрямую влияют на производительность приложений, корректность кода и удобство сопровождения на производственных серверах.
Если вы запускаете PHP-нагрузки в среде VPS Хостинга или на Выделенном сервере, понимание каждого изменения в PHP 8.3 не является опциональным — это обязательное условие для принятия обоснованных решений об обновлении, предотвращения незаметных регрессий и получения измеримого прироста производительности.
Что изменилось между PHP 8.2 и PHP 8.3
Прежде чем переходить к отдельным функциям, стоит определить масштаб этого релиза. PHP 8.3 — не кардинальная переработка. Это точечное обновление, которое закрывает давние пробелы в системе типов, укрепляет конвейер JIT и добавляет вспомогательные функции, для которых ранее требовались пользовательские обходные решения. В таблице ниже наиболее значимые изменения сопоставлены с их эквивалентами в PHP 8.2.
| Функция / Поведение | PHP 8.2 | PHP 8.3 |
|---|---|---|
| Типизированные константы классов | Не поддерживается | Полностью поддерживается |
json_validate() | Недоступно | Доступно нативно |
| Клонирование readonly-свойств | Невозможно | Поддерживается через clone |
array_is_list() | Доступно | Поведение не изменилось, но более широкие паттерны использования |
| Динамическое получение константы класса | Синтаксическая ошибка | Поддерживается через ClassName::{$const} |
Randomizer::getBytesFromString() | Недоступно | Доступно |
Randomizer::getFloat() / nextFloat() | Недоступно | Доступно |
Атрибут #[Override] | Недоступно | Доступно |
Устаревание: неявное задание seed в mt_rand | Не устарело | Устарело |
| Директива ini для размера стека Fiber | Не настраивается | Добавлено fiber.stack_size |
| Улучшения трассировки JIT | Базовая трассировка | Улучшенные IR и обработка циклов |
str_contains с массивами | Не поддерживается | По-прежнему не поддерживается (ошибка в исходной статье — см. ниже) |
> Важное исправление: В исходной статье ошибочно утверждается, что str_contains() принимает массив строк в PHP 8.3. Это фактически неверно. str_contains() принимает только два строковых аргумента. Передача массива вызывает TypeError. Правильный подход для поиска подстроки в нескольких строках — использование array_filter() в сочетании с str_contains() или in_array() для точных совпадений.
JIT-компиляция в PHP 8.3: что действительно изменилось
Предыстория: как работает PHP JIT
JIT-компилятор PHP, экспериментально введённый в PHP 8.0, работает как расширение подсистемы OPcache. Он компилирует горячие пути байткода в нативный машинный код во время выполнения, обходя интерпретатор Zend VM для этих путей. PHP 8.3 поставляется с существенно переработанным бэкендом JIT, который улучшает промежуточное представление (IR), используемое при компиляции.
Новый JIT на основе IR в PHP 8.3 (разработанный Дмитрием Стоговым) заменяет уровень генерации кода старого трассирующего JIT полноценным промежуточным представлением в форме SSA. Это обеспечивает лучшее распределение регистров, устранение мёртвого кода и подъём инвариантов циклов — оптимизации, которые были структурно невозможны в предыдущей архитектуре.
Правильное включение JIT
В исходной статье показано php -d jit=on script.php, что является неполным. JIT требует активного OPcache. Правильная минимальная конфигурация для CLI-бенчмарка или производственного php.ini:
; php.ini
opcache.enable=1
opcache.enable_cli=1
opcache.jit_buffer_size=128M
opcache.jit=tracingВ контексте веб-сервера (FPM или Apache mod_php) opcache.enable_cli не имеет значения, но opcache.jit_buffer_size должен быть ненулевым, иначе JIT молча отключается. Распространённая производственная ошибка — установка jit_buffer_size=0 в общем php.ini и недоумение, почему JIT не даёт эффекта.
Когда JIT даёт измеримый прирост
JIT не является универсально полезным. Его преимущества сосредоточены в нагрузках, ограниченных CPU:
- Высокоэффективные сценарии: математические вычисления, обработка изображений, инференс машинного обучения, игровая логика, циклы разбора CSV/данных, криптографические операции в пользовательском коде.
- Малоэффективные сценарии: типичные CRUD веб-приложения, где узким местом является I/O (запросы к базе данных, файловая система, сеть). В этих случаях накладные расходы JIT на компиляцию могут незначительно увеличить использование памяти при пренебрежимо малом улучшении пропускной способности.
- Отрицательные случаи: приложения с крайне разнообразными путями кода (крупные фреймворки с интенсивным использованием рефлексии) могут вызвать переполнение буфера JIT, что приведёт к накладным расходам на деоптимизацию.
Практическое правило: сначала выполните бенчмарк с opcache.jit=tracing. Если вы видите менее 3% улучшения на вашей реальной нагрузке, отключите JIT, чтобы освободить память буфера для кэша опкодов OPcache, который равномерно улучшает все PHP-приложения.
Типизированные константы классов
Это, пожалуй, наиболее значимое дополнение к системе типов в PHP 8.3 для крупных кодовых баз.
Какую проблему это решает
До PHP 8.3 константы классов не имели принудительного типа. Дочерний класс мог переопределить константу с полностью несовместимым типом, и PHP не выдавал ошибку до времени выполнения — или вовсе не выдавал, в зависимости от того, как использовалась константа.
// PHP 8.2 — no type enforcement
interface StatusCode {
const SUCCESS = 200; // implicitly int
}
class BrokenStatus implements StatusCode {
const SUCCESS = "two hundred"; // silently accepted — a maintenance nightmare
}Решение в PHP 8.3
// PHP 8.3 — type is enforced at definition and inheritance
interface StatusCode {
const int SUCCESS = 200;
}
class BrokenStatus implements StatusCode {
const int SUCCESS = "two hundred";
// Fatal error: Cannot use string as value for typed class constant
// BrokenStatus::SUCCESS of type int
}Все скалярные типы (int, float, string, bool), array, null, union-типы и intersection-типы допустимы для объявления типов констант. Типы never и void не разрешены. Эта функция чисто интегрируется с инструментами статического анализа, такими как PHPStan и Psalm, обеспечивая более строгие контракты интерфейсов без накладных расходов во время выполнения.
Динамическое получение констант классов и членов перечислений
PHP 8.3 позволяет получать константы классов и члены перечислений с использованием выражения времени выполнения в синтаксисе ::{}.
class Direction {
const string NORTH = 'north';
const string SOUTH = 'south';
}
$direction = 'NORTH';
echo Direction::{$direction}; // outputs: northРанее для этого требовалось constant() или выражение match, оба из которых многословны и подвержены ошибкам. Новый синтаксис также работает с перечислениями:
enum Color {
case Red;
case Blue;
}
$name = 'Red';
$color = Color::{$name}; // Color::RedГраничный случай, на который стоит обратить внимание: если переменная содержит имя, не соответствующее определённой константе или варианту перечисления, PHP выбрасывает исключение Error — не предупреждение. Оборачивайте динамические обращения в блок try/catch или проверяйте с помощью defined() / enum_exists() перед использованием.
Функция json_validate()
Почему это важно в продакшене
До PHP 8.3 идиоматический способ проверки JSON-строки без её декодирования выглядел так:
json_decode($input);
$isValid = json_last_error() === JSON_ERROR_NONE;Этот подход полностью декодирует JSON в PHP-структуру, выделяя память пропорционально размеру полезной нагрузки. Для конвейеров только с валидацией — API-шлюзов, потребителей очередей сообщений, получателей вебхуков — это расточительно.
Нативная валидация в PHP 8.3
$payload = '{"user": "alex", "role": "admin"}';
if (json_validate($payload)) {
// safe to decode
$data = json_decode($payload, true);
}
// Invalid JSON
var_dump(json_validate('{invalid}')); // bool(false)json_validate() разбирает структуру JSON без построения дерева PHP-значений. Потребление памяти составляет O(глубина) вместо O(размер), что делает её значительно более эффективной для больших полезных нагрузок. Она также принимает параметры $depth и $flags, согласующиеся с json_decode().
Реальный сценарий использования: получатель вебхуков, обрабатывающий 50 000 запросов в минуту, может использовать json_validate() для отклонения некорректных полезных нагрузок на входе до выполнения какой-либо десериализации, существенно снижая нагрузку на CPU и память.
Readonly-свойства: поддержка глубокого клонирования
PHP 8.2 ввёл readonly-свойства, но сделал их невозможными для изменения даже при клонировании объектов. Это вынуждало разработчиков прибегать к неудобным обходным решениям — фабричным методам, хакам с сериализацией или полному отказу от readonly для объектов-значений.
PHP 8.3 решает эту проблему: магический метод __clone() теперь допускает переназначение readonly-свойств в контексте клонирования.
class ImmutablePoint {
public function __construct(
public readonly float $x,
public readonly float $y,
) {}
public function withX(float $x): static {
$clone = clone $this;
$clone->x = $x; // Legal in PHP 8.3 within __clone context
return $clone;
}
}
$point = new ImmutablePoint(1.0, 2.0);
$moved = $point->withX(5.0);
echo $moved->x; // 5.0
echo $point->x; // 1.0 — original unchangedЭтот паттерн является основополагающим для неизменяемых объектов-значений в предметно-ориентированном проектировании. Без него readonly был в значительной мере декоративным для сложных доменных моделей.
Атрибут #[Override]
Атрибут #[Override] сигнализирует PHP (и инструментам статического анализа), что метод предназначен для переопределения метода родительского класса или интерфейса. Если родительский метод не существует, PHP выбрасывает ошибку времени компиляции.
class Base {
public function process(): void {}
}
class Child extends Base {
#[Override]
public function process(): void {
// If Base::process() is renamed or removed, this becomes a fatal error
}
}Это особенно ценно в крупных командах, где рефакторинг базового класса может незаметно нарушить переопределения в дочерних классах. Атрибут действует как контракт времени компиляции, перехватывая категорию ошибок, которые ранее проявлялись только во время выполнения или через статический анализ.
array_is_list() и корректная классификация массивов
array_is_list() был введён в PHP 8.1, а не в 8.3. Однако корректные паттерны его использования заслуживают точной документации, поскольку эта функция часто понимается неправильно.
PHP-массив является списком тогда и только тогда, когда:
- Он пуст, или
- Его ключи — последовательные целые числа, начинающиеся с
0без пропусков.
var_dump(array_is_list([])); // bool(true)
var_dump(array_is_list([0 => 'a', 1 => 'b'])); // bool(true)
var_dump(array_is_list(['a', 'b', 'c'])); // bool(true)
var_dump(array_is_list([1 => 'a', 0 => 'b'])); // bool(false) — wrong order
var_dump(array_is_list([0 => 'a', 2 => 'b'])); // bool(false) — gap at index 1
var_dump(array_is_list(['key' => 'value'])); // bool(false) — string keyПрактическое применение: при сериализации данных в JSON array_is_list() определяет, должен ли результат быть JSON-массивом ([]) или JSON-объектом ({}). Использование его перед json_encode() предотвращает случайную сериализацию в объект числово-индексированных массивов, из которых были удалены элементы.
Новые методы Randomizer в PHP 8.3
Класс RandomRandomizer, введённый в PHP 8.2, получает три важных дополнения:
getBytesFromString()
$randomizer = new RandomRandomizer();
$token = $randomizer->getBytesFromString('abcdefghijklmnopqrstuvwxyz0123456789', 16);
echo $token; // e.g., "k3mz9xqp1wvn7yt2"Это генерирует криптографически стойкую случайную строку из указанного алфавита — паттерн, необходимый для генерации токенов, OTP-кодов и создания слагов. Ранее для этого требовался ручной цикл с random_int().
getFloat() и nextFloat()
$randomizer = new RandomRandomizer();
// Returns a float in [0.0, 1.0)
$value = $randomizer->nextFloat();
// Returns a float in a specified closed or half-open interval
$scaled = $randomizer->getFloat(1.5, 9.5, RandomIntervalBoundary::ClosedOpen);getFloat() использует алгоритм γ-сечения для получения равномерно распределённых чисел с плавающей точкой без смещения по модулю, характерного для наивных реализаций. Это критически важно для симуляций, вероятностных алгоритмов и фреймворков A/B-тестирования, где важна равномерность распределения.
Устаревания и удаления в PHP 8.3
Понимание того, что выводится из использования, не менее важно, чем знание того, что добавляется. Игнорирование устареваний сейчас означает фатальные ошибки в PHP 9.0.
| Устаревшая функция | Причина | Путь миграции |
|---|---|---|
Вызов mt_rand() без явного seed в некоторых контекстах | Непоследовательность поведения неявного задания seed | Используйте RandomRandomizer |
ReflectionProperty::setValue() без объекта для нестатического метода | Неоднозначное поведение | Передавайте целевой объект явно |
Передача отрицательного $widths в mb_strimwidth() | Неопределённое поведение | Проверяйте входные данные перед вызовом |
ldap_connect() с отдельными аргументами host/port | Устарело в пользу формы URI | Используйте строку URI ldap://host:port |
range() с нецелочисленным шагом, дающим числа с плавающей точкой | Неожиданное неявное приведение типов | Явно приводите шаг к float |
Бенчмарки производительности: PHP 8.3 против предыдущих версий
На основе опубликованных бенчмарков от Kinsta, Phoronix и команды PHP internals с использованием Symfony Demo, WordPress и чистых нагрузок Fibonacci/сортировка:
| Бенчмарк | PHP 8.1 | PHP 8.2 | PHP 8.3 |
|---|---|---|---|
| Symfony Demo (запросов/сек) | ~1 450 | ~1 520 | ~1 610 |
| WordPress (запросов/сек) | ~1 180 | ~1 240 | ~1 290 |
| Fibonacci (JIT, мс) | ~48 | ~44 | ~38 |
| Mandelbrot (JIT, мс) | ~210 | ~195 | ~170 |
| Только OPcache (без JIT) | Базовый | +5% | +8% |
Прирост стабилен, но не драматичен для приложений, ограниченных I/O. Улучшения JIT в PHP 8.3 показывают наиболее значительную разницу в нагрузках с чистыми вычислениями — до 18% быстрее, чем PHP 8.2, на бенчмарке Mandelbrot.
Обновление до PHP 8.3: практический чеклист на стороне сервера
Если вы управляете собственной серверной инфраструктурой — будь то VPS с cPanel или физический Выделенный сервер — следуйте этой последовательности перед обновлением производственных сред.
Шаги перед обновлением
- Запустите
composer outdatedи обновите все зависимости до версий с задекларированной совместимостью с PHP 8.3. - Выполните
php -d error_reporting=E_ALL your_app_entrypoint.phpв CLI PHP 8.3, чтобы выявить уведомления об устаревании до того, как они станут фатальными ошибками. - Проверьте любой код, вызывающий
str_contains(),str_starts_with()илиstr_ends_with()с нестроковыми аргументами — теперь они выбрасываютTypeErrorв строгих контекстах. - Проверьте все константы классов, которые переопределяются в дочерних классах — типизированные константы вызовут фатальные ошибки при несовместимости типов.
- Проверьте вывод
phpinfo()после обновления, чтобы убедиться, что OPcache и JIT активны с ожидаемой конфигурацией.
Настройка php.ini для PHP 8.3 в продакшене
; Recommended production baseline for PHP 8.3
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.jit=tracing
opcache.jit_buffer_size=64MУстановка validate_timestamps=0 отключает проверку изменений файлов при каждом запросе — это необходимо для высоконагруженных развёртываний, где накладные расходы на инвалидацию OPcache измеримы. Вместо этого используйте хуки развёртывания для вызова opcache_reset() после деплоя кода.
Валидация после обновления
# Verify active PHP version
php -v
# Confirm JIT is compiled in and active
php -r "var_dump(opcache_get_status()['jit']);"
# Check for deprecation notices in error log
tail -f /var/log/php-fpm/error.log | grep -i deprecatPHP 8.3 и соображения безопасности веб-приложений
PHP 8.3 не вводит новых примитивов безопасности, но ряд изменений имеет косвенные последствия для безопасности:
json_validate()сокращает поверхность атаки в конвейерах валидации входных данных, предотвращая попадание некорректного JSON в логику десериализации.- Типизированные константы классов предотвращают атаки путаницы типов, при которых подкласс подставляет неожиданный тип для константы, связанной с безопасностью (например, уровень разрешений или значение тайм-аута).
- Атрибут
#[Override]предотвращает незаметное затенение методов в базовых классах, критически важных для безопасности, — вектор для тонких ошибок эскалации привилегий в архитектурах плагинов. - Дополнения
RandomRandomizerзаменяют небезопасные паттерны, такие какsubstr(str_shuffle(implode(range('a','z'))), 0, 16), для генерации токенов.
Для приложений, обрабатывающих конфиденциальные данные, сочетание PHP 8.3 с правильно настроенным стеком TLS является обязательным. Если в вашей хостинговой среде ещё не развёрнуты актуальные SSL-сертификаты, устраните это до любого обновления PHP.
Выбор подходящей хостинговой среды для PHP 8.3
JIT-компилятор PHP 8.3 и возросшие требования к памяти для разрешения типизированных констант означают, что среды с ограниченными ресурсами могут не получить полной выгоды от обновления.
- Виртуальный хостинг: доступность версий PHP полностью зависит от провайдера. Если вам нужен PHP 8.3 немедленно, планы Виртуального веб-хостинга с переключением версий PHP дают гибкость без накладных расходов на управление сервером.
- VPS: полный контроль над
php.ini, конфигурацией пулов PHP-FPM, настройкой OPcache и размером буфера JIT. Это минимально рекомендуемая среда для производственных развёртываний PHP 8.3 с включённым JIT. - Выделенные серверы: необходимы для высоконагруженных приложений, где конкуренция за буфер JIT между несколькими воркерами PHP-FPM становится узким местом. Выделенная среда также позволяет использовать NUMA-aware выделение памяти для OPcache.
- GPU-хостинг: актуален, если ваше PHP-приложение оркестрирует нагрузки с GPU-ускорением (например, вызывает сервисы инференса ML на Python). Среды GPU-хостинга выигрывают от улучшенного FFI и управления процессами в PHP 8.3.
Ключевые технические выводы и матрица решений
Используйте типизированные константы классов немедленно, если:
- Ваша кодовая база использует интерфейсы или абстрактные классы с константами, которые переопределяются в дочерних классах.
- Вы используете PHPStan уровня 8 или Psalm — типизированные константы открывают более строгий анализ.
Включайте JIT, если:
- Ваш профилировщик показывает, что время CPU превышает 40% времени запроса.
- Вы выполняете пакетную обработку, преобразование данных или математические нагрузки в PHP.
- У вас есть не менее 64 МБ выделенного буфера JIT OPcache на пул PHP-FPM.
Не включайте JIT, если:
- Ваше приложение ограничено I/O (база данных, кэш, файловая система, API-вызовы доминируют в задержке).
- Вы используете виртуальный хостинг с ограниченной памятью OPcache.
- Вы не проводили бенчмарк вашей конкретной нагрузки — не делайте предположений.
Используйте json_validate(), если:
- Вы где-либо в кодовой базе валидируете JSON перед декодированием.
- Вы обрабатываете высокообъёмные полезные нагрузки вебхуков или очередей сообщений.
Добавляйте #[Override] к:
- Каждому методу в дочернем классе, который намеренно переопределяет метод родителя.
- Переопределениям методов, критически важных для безопасности, в архитектурах плагинов или расширений.
Переходите на RandomRandomizer, если:
- Любая часть вашего кода использует
rand(),mt_rand(),array_rand()илиstr_shuffle()для генерации токенов или ключей, связанных с безопасностью.
FAQ
Нарушает ли PHP 8.3 обратную совместимость с кодом PHP 8.2?
В большинстве случаев нет. PHP 8.3 — это минорный релиз без удалённых функций из стандартной библиотеки, которые не были уже устаревшими в 8.2. Однако вновь устаревшие поведения будут генерировать уведомления E_DEPRECATED, и любой код, полагающийся на неявное приведение типов в константах или range() с шагом в виде числа с плавающей точкой, может вести себя иначе. Всегда запускайте тестовый набор под PHP 8.3 перед развёртыванием.
Включён ли JIT по умолчанию в PHP 8.3?
Нет. JIT требует включённого OPcache и ненулевого значения opcache.jit_buffer_size. Стандартный php.ini, поставляемый с большинством дистрибутивов, устанавливает opcache.jit_buffer_size=0, что фактически отключает JIT. Вы должны явно его настроить.
Можно ли использовать типизированные константы классов с union-типами в PHP 8.3?
Да. const int|string VERSION = 8; допустимо. Intersection-типы также разрешены для констант объектного типа. Единственные запрещённые типы — void и never.
В чём разница между json_validate() и json_decode() для целей валидации?
json_validate() разбирает структуру JSON без построения PHP-значения в памяти. Она значительно эффективнее по памяти для больших полезных нагрузок и быстрее, когда вам нужно лишь подтвердить структурную корректность. json_decode() следует использовать, когда вам действительно нужны декодированные данные — не вызывайте оба в последовательности; вызывайте json_validate() только тогда, когда намерены отбросить результат.
Поддерживает ли PHP 8.3 readonly-классы, введённые в PHP 8.2?
Да, и расширяет их. PHP 8.3 позволяет переназначать readonly-свойства внутри __clone(), что было основным ограничением readonly-классов в PHP 8.2. Это делает паттерны неизменяемых объектов-значений полностью жизнеспособными без обходных решений.
