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 Хостинг или Dedicated сървър, разбирането на всяка промяна в 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]Не е наличноНалично
Остаряло: неявно задаване на начална стойност на 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), използвано по време на компилация.

Новият IR-базиран JIT в PHP 8.3 (разработен от Dmitry Stogov) замества слоя за генериране на код на по-стария трасиращ 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, позволявайки по-строги интерфейсни договори без режийни разходи по време на изпълнение.

Динамично извличане на константа на клас и член на Enum

PHP 8.3 позволява извличане на константи на класове и членове на enum с помощта на израз по време на изпълнение в синтаксиса ::{}.

class Direction {
    const string NORTH = 'north';
    const string SOUTH = 'south';
}

$direction = 'NORTH';
echo Direction::{$direction}; // outputs: north

Преди това се изискваше constant() или израз match, и двата от които са многословни и склонни към грешки. Новият синтаксис работи и с enum:

enum Color {
    case Red;
    case Blue;
}

$name = 'Red';
$color = Color::{$name}; // Color::Red

Граничен случай за внимание: Ако променливата съдържа име, което не съответства на дефинирана константа или случай на enum, 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

Този модел е основополагащ за неизменяеми обекти-стойности в Domain-Driven Design. Без него 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 кодове и създаване на slug. Преди това се изискваше ръчен цикъл с 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() използва алгоритъма γ-section за производство на равномерно разпределени числа с плаваща запетая без модулното отклонение, което засяга наивните реализации. Това е критично за симулации, вероятностни алгоритми и A/B тестови фреймуърци, при които равномерността на разпределението е от значение.

Остарели функции и премахвания в PHP 8.3

Разбирането на това, което се премахва постепенно, е също толкова важно, колкото и знанието на това, което се добавя. Игнорирането на остарелите функции сега означава фатални грешки в PHP 9.0.

Остаряла функцияПричинаПът за миграция
Извикване на mt_rand() без явна начална стойност в някои контекстиНепоследователност в поведението при неявно задаване на начална стойностИзползвайте RandomRandomizer
ReflectionProperty::setValue() без обект върху нестатиченНеясно поведениеПодайте целевия обект изрично
Подаване на отрицателен $widths на mb_strimwidth()Недефинирано поведениеВалидирайте входа преди извикване
ldap_connect() с отделни аргументи за хост/портОстаряло в полза на 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 или на bare-metal Dedicated сървър — следвайте тази последователност преди надграждане на производствени среди.

Стъпки преди надграждане

  • Изпълнете composer outdated и актуализирайте всички зависимости до версии с декларирана съвместимост с PHP 8.3.
  • Изпълнете php -d error_reporting=E_ALL your_app_entrypoint.php под PHP 8.3 CLI, за да разкриете известия за остарели функции, преди да станат фатални грешки.
  • Проверете за код, който извиква 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 са измерими. Вместо това използвайте deployment hooks за извикване на 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.
  • Dedicated сървъри: Необходими за приложения с голям трафик, при които конкуренцията за JIT буфер между множество PHP-FPM работници се превръща в тесно място. Dedicated средата позволява също NUMA-съобразено разпределение на паметта за OPcache.
  • GPU хостинг: Релевантно, ако вашето PHP приложение оркестрира GPU-ускорени натоварвания (напр. извикване на Python ML inference услуги). Средите за GPU хостинг се възползват от подобрения FFI и управление на процесите в PHP 8.3.

Ключови технически изводи и матрица за вземане на решения

Използвайте типизирани константи на класове незабавно, ако:

  • Вашата кодова база използва интерфейси или абстрактни класове с константи, които дъщерни класове заместват.
  • Използвате PHPStan ниво 8 или Psalm — типизираните константи отключват по-строг анализ.

Активирайте JIT, ако:

  • Вашият профайлър показва, че CPU времето надвишава 40% от времето на заявката.
  • Изпълнявате пакетна обработка, трансформация на данни или математически натоварвания в PHP.
  • Разполагате с поне 64 MB dedicated OPcache JIT буфер на PHP-FPM пул.

Не активирайте JIT, ако:

  • Вашето приложение е ограничено от I/O (база данни, кеш, файлова система, API извиквания доминират латентността).
  • Използвате споделен хостинг с ограничена памет за OPcache.
  • Не сте направили бенчмарк на конкретното си натоварване — не приемайте нищо за даденост.

Приемете json_validate(), ако:

  • Валидирате JSON преди декодиране навсякъде в кодовата си база.
  • Обработвате уебхук или полезни товари от опашки за съобщения с голям обем.

Добавете #[Override] към:

  • Всеки метод в дъщерен клас, който умишлено замества метод на родителски клас.
  • Заместванията на методи, критични за сигурността, в архитектури с плъгини или разширения.

Мигрирайте към RandomRandomizer, ако:

  • Някоя част от кода ви използва rand(), mt_rand(), array_rand() или str_shuffle() за генериране на токени или ключове, свързани със сигурността.

ЧЗВ

Нарушава ли 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
За начало