15%

Сэкономьте 15% на всех хостинговых услугах

Проверьте свои навыки и получите скидку на любой тарифный план

Используйте код:

Skills
Начать
05.12.2023

Новые функции и улучшения в 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.2PHP 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-массив является списком тогда и только тогда, когда:

  1. Он пуст, или
  2. Его ключи — последовательные целые числа, начинающиеся с 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.1PHP 8.2PHP 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 deprecat

PHP 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. Это делает паттерны неизменяемых объектов-значений полностью жизнеспособными без обходных решений.

15%

Сэкономьте 15% на всех хостинговых услугах

Проверьте свои навыки и получите скидку на любой тарифный план

Используйте код:

Skills
Начать