<? phpukraine СПІВБЕСІДИ
Пошук по платформі
CORE PHP · SENIOR

Що нового в PHP 8.5 і що з цього ви вже використали б?

PHP 8.5 (реліз 20 листопада 2025) додав оператор конвеєра `|>`, синтаксис `clone $obj with {...}`, атрибут `#[\NoDiscard]` з приведенням `(void)`, вбудоване розширення URI з класами `Uri\Rfc3986\Uri` та `Uri\WhatWg\Url`, а також дрібніші зручності — `array_first()`/`array_last()`, атрибути на константах, замикання в константних виразах і бектрейси для фатальних помилок.

Ви вже дивилися на 8.5? Що з нового реально взяли б у продакшн, а що ні?
Що робить оператор `|>` і чим він кращий за вкладені виклики функцій?
Навіщо `clone with`, якщо вже є `__clone()` і звичайні `with*()`-методи?
Чому нам більше не радять `parse_url()` і що прийшло на заміну?
PHP 8.5 pipe operator clone with NoDiscard ext-uri upgrade

PHP 8.5 вийшов 20 листопада 2025 року, і за структурою це типовий мінорний реліз: три зміни, які видно прямо в синтаксисі (|>, clone with, #[\NoDiscard]), одне велике доповнення стандартної бібліотеки (розширення URI) і десяток дрібниць — array_first()/array_last(), атрибути на константах, замикання в константних виразах, final для просунутих у конструкторі властивостей, get_error_handler()/get_exception_handler(), бектрейси для фатальних помилок, персистентні share-хендли cURL. Сильна відповідь на це питання — не перелік, а сортування: що змінює те, як ви пишете код щодня, що закриває конкретний клас багів, а що є нішевим і чекатиме свого випадку.

Оператор конвеєра |> розвертає ланцюжок викликів зліва направо: замість htmlspecialchars(trim($raw)) пишеться $raw |> trim(...) |> htmlspecialchars(...). Механіка проста і має рівно одну пастку: права частина — це вираз, значення якого має бути callable, і викликається він з одним аргументом. Тому там стоїть синтаксис callable першого класу trim(...) (PHP 8.1), замикання або змінна з callable, але не trim() — такий запис викличе функцію без аргументів. Функції з параметром за посиланням у конвеєр не передаються взагалі, а для функцій із кількома обовʼязковими аргументами доводиться загортати виклик у fn, бо часткового застосування (str_replace('a', 'b', ?)) у PHP немає — відповідний RFC відхилили. Пріоритет у |> нижчий за арифметику й конкатенацію, тож 'x' . $y |> f(...) спершу склеїть рядки, а вже потім передасть їх далі; у сумнівних місцях (особливо поруч із ?? і тернарником) простіше поставити дужки. І окремо: жодного «пропускати null», як у ?->, тут немає — null спокійно піде в наступну ланку і впаде вже на її типі.

clone with закриває найнудніший бойлерплейт незмінних обʼєктів. До 8.5 wither писався як new static($this->a, $this->b, $new, $this->d) — і кожна нова властивість вимагала правки всіх таких методів. Тепер return clone $this with {url: $url}; копіює обʼєкт і присвоює лише перелічені властивості. Важливі деталі: конструктор не викликається, тому валідація з нього не спрацює; копіювання лишається поверхневим, тож вкладені мутабельні обʼєкти й далі спільні і глибоку копію робить __clone(), який викликається вже після присвоєння нових значень; readonly-властивість змінити так можна, але лише з коду, який має право її записувати, тобто зсередини класу-власника — ззовні буде Error: Cannot modify readonly property. Це саме той випадок, коли фічу варто брати одразу: вона не змінює семантику, лише прибирає рядки, які й так були помилконебезпечними.

Атрибут #[\NoDiscard] позначає функцію чи метод, чий результат не можна мовчки викидати; разом із ним у мову додали приведення (void), яким ігнорування оформлюють свідомо. Ефект — E_WARNING, а не виняток, тож це підказка, а не гарантія, і користь від неї є там, де попередження реально видно в логах і CI. Найкращі кандидати — withers незмінних обʼєктів і чисті перетворення, у яких виклик без присвоєння не робить нічого. Розширення URI — інша за вагою зміна: воно приносить Uri\Rfc3986\Uri (сувора граматика RFC 3986, resolve(), equals(), toString()/toRawString()) і Uri\WhatWg\Url (алгоритм браузерів, нормалізація хоста й IDN через getAsciiHost()/getUnicodeHost()). Обидва незмінні, з with*()-методами й без __toString(), а невдалий розбір — це Uri\InvalidUriException / Uri\WhatWg\InvalidUrlException у конструкторі або null зі статичного parse(). Практичний сенс — вибір стандарту стає явним: parse_url() не відповідає жодному з них, лише розрізає рядок і мовчки ковтає значну частину сміття, а розбіжність між вашою перевіркою і тим, куди насправді піде браузер, — класичне джерело SSRF-обходів.

Межі варто назвати чесно. Новий синтаксис — це підвищення мінімальної версії: для застосунку, який ви деплоїте самі, це питання CI й версії на серверах, а для бібліотеки з зовнішніми користувачами |> або clone with у коді означає відсікти всіх, хто ще на 8.3–8.4. Перед першим рядком нового синтаксису переконайтесь, що всі інструменти, які парсять код — статичний аналізатор, Pint/PHP-CS-Fixer, генератори документації, — уже вміють 8.5, бо інакше вони не розберуть файл цілком. І тримайте пропорцію: clone with і розширення URI дають користь одразу і майже без ризику, #[\NoDiscard] — точково на кількох ключових API, а |> варто вводити лише там, де ланцюжок справді складається з унарних функцій; конвеєр із трьох fn поспіль читається гірше, ніж три звичайні змінні з іменами.

use Uri\InvalidUriException;
use Uri\Rfc3986\Uri;

final class Link
{
    public function __construct(
        public readonly string $url,
        public readonly string $host,
    ) {}

    /** clone with: решту властивостей не перелічуємо, readonly пишемо зі scope класу */
    public function withUrl(string $url): static
    {
        return clone $this with {url: $url};
    }
}

// Виклик, чий результат відкинули, дасть E_WARNING саме з цим поясненням
#[\NoDiscard('поверніть нормалізований рядок: сам виклик нічого не змінює')]
function normalize(string $raw): string
{
    // праворуч від |> має стояти ВИРАЗ-callable: trim(...) або замикання
    return $raw
        |> trim(...)
        |> fn (string $u): string => rtrim($u, '/');
}

$raw = '  https://Example.COM/docs/?utm_source=x#top  ';

try {
    // сувора граматика RFC 3986; «як у браузері» — Uri\WhatWg\Url
    $uri = new Uri(normalize($raw));
} catch (InvalidUriException $e) {
    throw new InvalidArgumentException('Некоректне посилання', previous: $e);
}

// null прибирає компонент; __toString() у класу немає — лише явний toString()
$canonical = $uri->withQuery(null)->withFragment(null)->toString();
$link = (new Link($uri->toString(), $uri->getHost() ?? ''))->withUrl($canonical);

normalize($raw);        // Warning: return value should either be used...
(void) normalize($raw); // свідоме ігнорування — попередження зникає
Що праворуч від `|>` стоїть вираз, який має дати callable, тому пишуть `trim(...)`, а не `trim()`; часткового застосування (partial application) у PHP немає, і для функцій із кількома аргументами доводиться писати `fn`.
Що `clone with` — це не глибоке копіювання, а звичайний `clone` із присвоєнням перелічених властивостей, і саме він прибирає бойлерплейт `with*()`-методів у readonly-DTO.
Що `#[\NoDiscard]` дає `E_WARNING`, а не помилку, і що свідоме ігнорування результату пишеться приведенням `(void)`.
Що розширення URI дає два різні набори правил — сувору RFC 3986 і браузерну WHATWG — і що `parse_url()` не відповідає жодному стандарту, тому для перевірки користувацького вводу він поганий вибір.
Що senior-відповідь містить план апгрейду: мінімальна версія в `composer.json`, матриця CI, статичний аналіз і форматер, які вміють парсити новий синтаксис, і вікно підтримки версії.
Що кандидат не вигадує фіч: наприклад, у PHP немає ані оператора `|>` з автоматичним пропуском `null`, ані вбудованого часткового застосування.
Писати `$x |> strtoupper()` замість `$x |> strtoupper(...)`: перший варіант викличе `strtoupper()` без аргументів і спробує використати результат як callable.
Вважати, що конвеєр «зупиняється на null», як `?->`: жодного короткого замикання немає — `null` просто піде в наступну ланку і там впаде на типі.
Пробувати `$array |> sort(...)`: функції з параметром за посиланням у конвеєр не передаються.
Розраховувати, що `clone $order with {status: ...}` спрацює ззовні класу для `readonly`-властивості — доступ на запис перевіряється так само, як при звичайному присвоєнні, і буде `Cannot modify readonly property`.
Думати, що `#[\NoDiscard]` кидає виняток або що ним уже позначені всі «небезпечні» функції стандартної бібліотеки — це opt-in-атрибут насамперед для вашого коду.
Інтерполювати обʼєкт URI в рядок: у нових класів немає `__toString()`, є явні `toString()`/`toRawString()` (і `toAsciiString()`/`toUnicodeString()` у WHATWG-версії).
Називати «новинками 8.5» речі з попередніх релізів — property hooks і асиметричну видимість (8.4), readonly-класи (8.2), `never`/enum (8.1).
ПОРАДА

Побудуйте відповідь як рішення, а не як список: «синтаксично найпомітніші три — `|>`, `clone with`, `#[\NoDiscard]`; найкорисніше для продакшну — розширення URI, бо воно прибирає самописну валідацію посилань поверх `parse_url()`; `clone with` беру одразу у withers, `|>` — точково, бо без часткового застосування він швидко перетворюється на ланцюжок `fn`».

оновлено 4 вересня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

Оператор обчислює праву частину як вираз і очікує від нього callable, тому потрібен синтаксис callable першого класу `strtoupper(...)`. Варіант `strtoupper()` викличе функцію без аргументів; рядок `'strtoupper'` теж є callable, але записаний без лапок він буде спробою прочитати константу.