PHP 8.4: Главные нововведения
PHP 8.4 — релиз «инструментов для взрослых»: он даёт новые способы выражать инварианты на границе свойств (Property Hooks, асимметричная видимость), продвигает внутренности фреймворков вперёд (Lazy Objects, новые Reflection-хелперы) и модернизирует проблемные зоны (WHATWG-совместимые классы Dom\*, корректный multipart-парсинг не только для POST). Параллельно ужесточаются крайние случаи (например, типовое поведение exit(), рекурсия при сравнениях) и намечается отказ от привычек, которые годами удивляли пользователей (implicit nullable params, lcg_value(), старые CSV-дефолты).
Содержание (Оглавление)
- Property Hooks (логика get/set у свойства)
- Асимметричная видимость (
public get,private set) - Lazy Objects (ghost/proxy через Reflection)
#[\\Deprecated]— deprecations в userland с хорошими сообщениями- Новый DOM:
Dom\\*(WHATWG) + улучшения XPath - Multipart не только в POST:
request_parse_body() - Новые функции, которые стоит взять в работу (array_find, fpow, mb_trim, …)
- Обратно несовместимые изменения (миграционные заметки)
- Deprecated (исправить до появления ошибок)
- Прочие изменения и эксплуатация (Fibers/GC, builtin server, bcrypt cost)
Property Hooks (логика get/set у свойства)
Свойства теперь могут определять хуки get и/или set. Это позволяет делать валидацию, нормализацию, вычисляемый доступ и правила хранения на уровне свойства — без россыпи методов и без утечки инвариантов по всему коду.
Типовые сценарии:
- Нормализация при записи (например, приведение регистра)
- Валидация при записи (ошибка как можно раньше)
- Вычисляемые read-only свойства (виртуальные / без 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 getters/setters и держат инварианты рядом с данными.
Асимметричная видимость (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 можно создавать объекты с отложенной инициализацией (до первого доступа). Это прежде всего для библиотек/фреймворков: контейнеры, ORM и генераторы прокси могут откладывать дорогую работу (IO, гидратацию, построение графа), но при этом возвращать корректно типизированный объект.
$initializer = static function ($obj) {
// initialize $obj lazily
};
$r = new ReflectionClass(Example::class);
$lazy = $r->newLazyGhost($initializer);
В Reflection добавлены методы и константы для проверки/управления lazy-состоянием (например, принудительно инициализировать, пропускать инициализацию при сериализации).
#[\\Deprecated] — deprecations в userland с хорошими сообщениями
Теперь можно помечать функции/методы/константы класса как deprecated через #[\Deprecated]. Это похоже на deprecations в ядре, но эмитится E_USER_DEPRECATED и сообщение можно сделать более понятным.
Для авторов библиотек это сильное улучшение: deprecations становятся предсказуемыми и единообразными, без самописных warning-хелперов.
Новый DOM: Dom\\* (WHATWG) + улучшения XPath
PHP 8.4 добавляет новое пространство имён Dom с аналогами классических DOM-классов (например Dom\Node вместо DOMNode). Цель — совместимость с HTML5 и соответствие WHATWG, чтобы закрыть старые проблемы DOM-расширения.
Также есть практичные улучшения:
DOMNode::compareDocumentPosition()и связанные константыDOMXPath::registerPhpFunctions()теперь принимает любой callableDOMXPath::registerPhpFunctionNs()поддерживает синтаксис нативного вызова (вместоphp:function('...'))
Если у вас есть пользовательские классы-наследники DOM, следите за новыми членами и совместимостью сигнатур (возможны 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 для крайних случаев возведения в степень, 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() параметр режима может быть RoundingMode (в дополнение к старым PHP_ROUND_*), что лучше читается в деньгах и отчётах, чем «магические» константы.
$lineTotal = round($amount * $qty, 2, RoundingMode::HalfAwayFromZero);
Перепроверьте тесты со снапшотами денежных сумм: реализация round() в 8.4 переписана, часть крайних случаев отличается от предыдущих версий.
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 байт).
E_STRICTудалён: уровень ошибок убран; константаE_STRICTтеперь deprecated.
Миграция resource → object (высокий риск для legacy)
Ряд расширений перевели свои “handles” с resources на objects. Проверки через is_resource() нужно заменить на проверку false/null согласно документации.
- DBA: теперь
Dba\Connectionвместоdba_connectionresource. - ODBC: теперь
Odbc\Connection/Odbc\Resultobjects. - SOAP:
SoapClient::$httpurlтеперьSoap\Url(null если отсутствует);SoapClient::$sdlтеперьSoap\Sdl(null если отсутствует).
Новые warnings и exceptions
Во многих местах неправильные аргументы теперь приводят к ValueError / TypeError вместо предупреждений или “как-нибудь отработало” (например, диапазоны quality/speed в GD, валидация round() mode, ограничения str_getcsv() по 1 байту, null bytes в XMLReader/Writer, проверки параметров XSL и т.д.). Это улучшает корректность, но может ломать код, который продолжал работу после warning.
Избранное по расширениям
- 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().
Стандартная библиотека / расширения
- DatePeriod ISO-string constructor: deprecated; используйте
DatePeriod::createFromISO8601String(). - DOM: константа
DOM_PHP_ERRdeprecated; ряд старых DOM-свойств формально deprecated. - Random:
lcg_value()deprecated; используйтеRandom\Randomizer::getFloat(). - Reflection:
ReflectionMethod::__construct()с одним аргументом 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; перестаньте переопределять и убедитесь, что storage поддерживает 32-символьные hex session IDs. Несколько session.* INI опций для trans-sid deprecated; константа SID deprecated.
Прочие изменения и эксплуатация (Fibers/GC, builtin server, bcrypt cost)
Что заметно в эксплуатации:
- Fibers & destructors: переключение fiber во время выполнения 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 на хеширование, если вы полагались на дефолты.- Rounding:
round()принимаетRoundingMode|int, добавлены новые режимы и исправлены edge-case баги — перепроверьте финансовую математику снапшотами.
Итог
Если вы пишете фреймворки, главный профит 8.4 — Property Hooks + асимметричная видимость + Lazy Objects: инварианты можно выразить ровно там, где данные меняются, а тяжёлую инициализацию отложить до реального доступа. Для прикладных команд миграция чаще всего упирается в более строгие ошибки (ValueError/TypeError), resource→object переходы и чистку deprecations (implicit nullable params, lcg_value(), CSV defaults) до того, как они станут жёсткими ошибками.