PHP 8.4: Основни нововъведения

PHP 8.4 е релийз с „power tools“: дава нови начини да изразявате инварианти на границата на свойствата (Property Hooks, асиметрична видимост), развива вътрешностите на framework-ите (Lazy Objects, нови Reflection helper-и) и модернизира проблемни зони (WHATWG-съвместими класове Dom\*, multipart parsing и извън POST). Успоредно с това се затягат edge cases (например типово поведение на exit(), рекурсия при сравнения) и започва поетапно изваждане от употреба на шаблони, които често изненадват (implicitly-nullable параметри, lcg_value(), старите CSV defaults).

Съдържание


Property Hooks (логика get/set върху свойства)

Свойствата вече могат да имат get и/или set hooks. Така валидацията, нормализацията, computed access и правилата за storage са в самото свойство — без отделни getter/setter методи и без разпръснати инварианти.

Типични шаблони:

  • Нормализация при запис (напр. 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 слоеве и framework-и 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 позволява обекти с отложена инициализация (до първи достъп). Това е насочено към библиотеки/framework-и: DI containers, ORM-и и proxy генератори могат да отложат скъпа работа (IO, hydration, graph build), но да върнат правилно типизиран обект.

$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 и съобщенията са по-четими.

За автори на библиотеки това е голямо улеснение: deprecation-ите стават предвидими и последователни.

Нов 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() поддържа native function call синтаксис (вместо php:function('...'))

Ако наследявате DOM класове в userland, внимавайте за нови членове и съвместимост на декларациите (възможни compile errors при конфликт на имена).

Multipart и извън POST: request_parse_body()

request_parse_body() позволява парсване на RFC1867 (multipart) тела в не-POST HTTP заявки. Полезно за API-та, които използват PUT/PATCH с multipart (файлове + form data).

Практичен набросък (server-side обработка):

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

Multibyte-safe trim (mb_trim())

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

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

В PHP 8.4 round() приема RoundingMode enum (освен старите PHP_ROUND_*), което е по-четимо в пари и отчети от „магически“ константи.

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

Претествайте snapshot тестове за суми: реализацията на round() в 8.4 е пренаписана и част от edge cases се различава от по-ранни версии PHP.

Deprecations в userland (#[\Deprecated])

Библиотеките могат да маркират API като core, с подсказка за замяна и since (не се валидира от PHP — използвайте semver или release tag).

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

Обратно несъвместими промени (миграционни бележки)

Core език/runtime

  • exit() / die(): по-функционално поведение (може да се подава като callable, влияе се от strict_types, работят стандартни coercions). Невалидни типове вече последователно хвърлят TypeError вместо да се cast-ват към string.
  • Рекурсия при сравнения: вече хвърля Error вместо фатален E_ERROR.
  • Readonly + __clone(): индиректна промяна чрез reference в __clone() вече не е позволена (напр. $ref = &$this->readonly).
  • Типове на константи: PHP_DEBUG и PHP_ZTS вече са bool (преди int).
  • Temp filenames: имената за 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() и related 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 — ретествайте финансови изчисления със snapshot-и.

Обобщение

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