PHP 8.4: Головні нововведення

PHP 8.4 — реліз «power tools»: він дає нові способи виражати інваріанти на межі властивостей (Property Hooks, асиметрична видимість), рухає внутрішності фреймворків уперед (Lazy Objects, нові Reflection helper’и) та модернізує проблемні зони (WHATWG-сумісні класи Dom\*, коректний multipart parsing не лише для POST). Водночас він підкручує edge cases (наприклад типову поведінку exit(), рекурсію під час порівнянь) і починає прибирати з обігу патерни, які роками дивували (implicitly-nullable параметри, lcg_value(), старі CSV defaults).

Зміст


Property Hooks (логіка get/set у властивості)

Властивості тепер можуть мати hooks get і/або set. Це дозволяє робити валідацію, нормалізацію, computed access і правила збереження на рівні властивості — без окремих методів і без «витоку» інваріантів по всьому коду.

Типові сценарії:

  • Нормалізація під час запису (наприклад, casing)
  • Валідація під час запису (fail fast)
  • Computed read-only властивості (virtual / без backing value)
final class Person
{
    public string $firstName {
        set => ucfirst(strtolower($value));
    }

    public string $lastName {
        set {
            if (strlen($value) < 2) {
                throw new InvalidArgumentException('Too short');
            }
            $this->lastName = $value;
        }
    }

    public string $fullName {
        get => $this->firstName . ' ' . $this->lastName;
    }
}

Для DTO-шарів і фреймворків hooks часто знімають тонни boilerplate та тримають інваріанти поруч із даними.

Асиметрична видимість (public get, private set)

Тепер можна контролювати видимість set окремо від get. Це практичний компроміс: читання відкрите, а запис обмежений (наприклад, лише всередині класу).

final class User
{
    public private(set) string $email;

    public function __construct(string $email)
    {
        $this->email = $email;
    }
}

Lazy Objects (ghost/proxy через Reflection)

У PHP 8.4 можна створювати об’єкти з відкладеною ініціалізацією (до першого доступу). Це насамперед для бібліотек/фреймворків: DI containers, ORM і proxy генератори можуть відкласти дорогу роботу (IO, hydration, побудову графа), але повернути коректно типізований об’єкт.

$initializer = static function ($obj) {
    // initialize $obj lazily
};

$r = new ReflectionClass(Example::class);
$lazy = $r->newLazyGhost($initializer);

Reflection отримав нові методи/константи для перевірки та керування lazy-станом (явна ініціалізація, skip initialization під час serialize тощо).

#[\\Deprecated] — deprecations у userland з хорошими повідомленнями

Тепер можна позначати userland функції/методи/константи класу як deprecated через #[\Deprecated]. Поведінка відповідає core deprecations, але емісія йде як E_USER_DEPRECATED, а повідомлення можна зробити більш читабельними.

Для авторів бібліотек це великий плюс: deprecations стають передбачуваними й однаковими.

Новий DOM: Dom\\* (WHATWG) + покращення XPath

PHP 8.4 вводить Dom namespace з аналогами класичних DOM-класів (наприклад Dom\Node замість DOMNode). Мета — краща HTML5 сумісність і WHATWG spec alignment, щоб закрити давні проблеми DOM extension.

Є й практичні покращення:

  • DOMNode::compareDocumentPosition() і пов’язані константи
  • DOMXPath::registerPhpFunctions() приймає будь-який callable
  • DOMXPath::registerPhpFunctionNs() підтримує нативний синтаксис виклику (замість php:function('...'))

Якщо ви наслідуєте DOM класи в userland, стежте за новими членами та сумісністю декларацій (можливі compile errors при конфлікті імен).

Multipart не лише в POST: request_parse_body()

request_parse_body() дозволяє парсити RFC1867 (multipart) тіла у не-POST HTTP запитах. Це важливо для API, які коректно використовують PUT/PATCH з multipart (файли + form data).

Практичний начерк (обробка запиту на сервері):

if ($_SERVER['REQUEST_METHOD'] === 'PATCH') {
    [$post, $files] = request_parse_body();
    // $post — поля форми, $files — завантажені файли (RFC1867)
}

Нові функції, які варто взяти в роботу (array_find, fpow, mb_trim, …)

Хайлайти зі списку нових функцій:

  • Core: request_parse_body()
  • Standard: array_all(), array_any(), array_find(), array_find_key(), fpow(), http_get_last_response_headers(), http_clear_last_response_headers()
  • MBString: mb_trim(), mb_ltrim(), mb_rtrim(), mb_ucfirst(), mb_lcfirst()
  • Intl: grapheme_str_split() і helper’и для time zones / parsing
  • BCMath: bcround(), bcceil(), bcfloor(), bcdivmod()
  • Opcache: opcache_jit_blacklist()

Якщо вам потрібні IEEE 754 semantics для edge cases під час піднесення до степеня, fpow() — рекомендована заміна для deprecated поведінки “(0) у від’ємному степені” в ** / pow().

Практичні рецепти

Знайти елемент без ручних циклів (array_find() / array_find_key())

$user = array_find($users, fn($u) => $u['id'] === $id);
if ($user === null) {
    throw new RuntimeException('User not found');
}

Перевірити колекції (array_any() / array_all())

if (array_any($orders, fn($o) => $o['status'] === 'failed')) {
    // alert / retry / short-circuit
}

$allPaid = array_all($orders, fn($o) => $o['status'] === 'paid');

IEEE 754 semantics для степенів (fpow())

// Deprecated у 8.4: 0 ** -2  (семантика ділення на нуль)
$x = fpow(0.0, -2.0); // IEEE 754 style result

Trim для multibyte рядків (mb_trim())

$name = mb_trim($name);
$title = mb_ucfirst(mb_trim($title));

Фінанси / явні режими округлення (RoundingMode + round())

У PHP 8.4 round() приймає enum RoundingMode (окрім старих PHP_ROUND_*), що краще читається в грошах і звітах, ніж «магічні» константи.

$lineTotal = round($amount * $qty, 2, RoundingMode::HalfAwayFromZero);

Переперевірте snapshot-тести сум: реалізацію round() у 8.4 переписано, частина крайніх випадків відрізняється від попередніх версій PHP.

Deprecations у userland (#[\Deprecated])

Бібліотеки можуть позначати API так само, як ядро PHP, із підказкою заміни та рядком since (PHP не валідує — використовуйте semver або тег релізу).

#[\Deprecated(message: 'Use findById()', since: '2.0')]
function find(): array
{
    // ...
}

Зворотно несумісні зміни (міграційні нотатки)

Ядро мови / runtime

  • exit() / die(): поводяться більш «як функція» (їх можна передавати як callable, на них впливає strict_types, працює стандартна coercion). Невірні типи тепер стабільно кидають TypeError замість приведення до рядка.
  • Рекурсія під час порівнянь: тепер Error замість фатального E_ERROR.
  • Readonly + __clone(): непряма модифікація через reference всередині __clone() заборонена (наприклад $ref = &$this->readonly).
  • Типи констант: PHP_DEBUG і PHP_ZTS тепер bool (раніше int).
  • Тимчасові імена файлів: імена upload/tempnam стали довші (+13 bytes).
  • E_STRICT прибрано: рівень помилок видалено; константа E_STRICT тепер deprecated.

Міграція resource → object (високий ризик для legacy перевірок)

Кілька extension-ів перейшли з resources на objects. Перевірки is_resource() потрібно замінити на перевірки false/null за документацією.

  • DBA: тепер Dba\Connection замість dba_connection resource.
  • ODBC: тепер Odbc\Connection / Odbc\Result objects.
  • SOAP: SoapClient::$httpurl тепер Soap\Url (null якщо відсутній); SoapClient::$sdl тепер Soap\Sdl (null якщо відсутній).

Нові warnings та exceptions

У багатьох місцях неправильні аргументи тепер дають ValueError / TypeError замість warnings або непослідовної поведінки (наприклад, валідація GD quality/speed, перевірка round() mode, обмеження на 1 byte в str_getcsv(), null bytes у XMLReader/Writer, валідації XSL тощо). Це покращує коректність, але може зламати код, який покладався на warnings.

Акценти по extension-ах

  • DOM: DOMImplementation::getFeature() видалено; клонування DOMXPath тепер кидає Error (раніше створювався непридатний клон).
  • GMP: клас GMP тепер final (не можна наслідувати).

Deprecated (виправити до того, як стануть помилками)

Core

  • Implicitly nullable parameters: function f(string $s = null) deprecated, бо неявно розширює тип. Використовуйте ?string $s = null (або переробіть сигнатуру, якщо далі йдуть обов’язкові параметри).
  • 0 ** -n / pow(0, -n): deprecated (семантика ділення на нуль). Для IEEE 754 використовуйте fpow().
  • Клас з ім’ям _: deprecated.
  • trigger_error(..., E_USER_ERROR): deprecated — краще exception або exit().

Стандартна бібліотека / extension-и

  • DatePeriod ISO-string constructor: deprecated; використовуйте DatePeriod::createFromISO8601String().
  • DOM: константа DOM_PHP_ERR deprecated; частина старих DOM properties формально deprecated.
  • Random: lcg_value() deprecated; використовуйте Random\Randomizer::getFloat().
  • Reflection: ReflectionMethod::__construct() з 1 аргументом deprecated; використовуйте ReflectionMethod::createFromMethodName().
  • MySQLi: mysqli_ping(), mysqli_kill(), mysqli_refresh() та пов’язані refresh-константи deprecated (reconnect прибрали; натомість SQL команди).
  • CSV defaults: default escape для fputcsv() / fgetcsv() / str_getcsv() deprecated — передавайте явно.
  • XML: xml_set_object() deprecated; передавання non-callable strings у xml_set_* deprecated.

Sessions (ops/security hygiene)

Зміна session.sid_length і session.sid_bits_per_character deprecated; припиніть override та переконайтесь, що storage backend приймає 32-char hex session IDs. Кілька session.* INI опцій для trans-sid deprecated; константа SID deprecated.

Інші зміни та експлуатація (Fibers/GC, builtin server, bcrypt cost)

Що помітно в експлуатації:

  • Fibers & destructors: fiber switching під час виконання destructor тепер дозволено; destructors, викликані GC, можуть виконуватися в окремому fiber (gc_destructor_fiber). Якщо ви змішуєте Fibers і важкі destructors — тестуйте уважно.
  • Builtin server: пошук index-файла тепер піднімається по батьківських директоріях навіть коли шлях “виглядає як файл” (крапка в останньому компоненті).
  • Apache: прибрано підтримку EOL Apache 2.0/2.2 (мінімум 2.4).
  • password_hash(): default bcrypt cost піднято з 10 до 12 — більше CPU, якщо покладались на defaults.
  • Rounding: round() приймає RoundingMode|int, додано нові режими та виправлено edge cases — перевірте фінансову математику снапшотами.

Підсумок

Для framework-команд найбільший виграш у 8.4 — Property Hooks + асиметрична видимість + Lazy Objects: інваріанти описуються саме там, де дані змінюються, а важку ініціалізацію можна відкласти до реального доступу. Для прикладних команд міграція здебільшого зводиться до суворіших помилок (ValueError/TypeError), переходів resource→object і прибирання deprecations (implicitly-nullable параметри, lcg_value(), CSV defaults) до того, як вони стануть жорсткими помилками.