PHP 8.6 виходить 19 листопада 2026 року. Feature freeze ще попереду, але основне вже у гілці, і цього разу найцікавіше - не окрема можливість, а те, як дві з них нарешті складаються докупи. Детальний огляд змін зібрав Брент Роозе; нижче - те, що з цього справді варто знати, звірене з відповідними RFC.
Часткове застосування функцій рятує оператор |>
Оператор |> приїхав ще в PHP 8.5, але користуватися ним було незручно: він вимагає callable рівно з одним параметром. Тобто strtolower(...) у пайплайн стає, а str_replace(' ', '-', ...) - ні, бо в неї три параметри. На практиці це означало замикання вручну на кожному другому кроці, і половина краси пайплайна зникала.
Часткове застосування функцій (partial function application, PFA) закриває саме цю дірку. Знак ? позначає параметр, який лишається незаповненим, а решта фіксується одразу:
$makeSlug = str_replace(' ', '-', ?);
$makeSlug('Hello World');
// Hello-World
Результат - звичайне замикання, яке можна передати куди завгодно. Але найкраще воно виглядає саме в парі з |>:
$output = 'Hello World'
|> str_replace(' ', '-', ?)
|> strtolower(...);
// hello-world
Автори RFC - Ларрі Гарфілд і Арно ле Блан, статус - Implemented. Якщо ви дивилися на пайплайн-оператор і не розуміли, навіщо він потрібен, то ось відповідь: без PFA він був напівфабрикатом.
Io\Poll: заміна stream_select
Друга велика річ - Polling API авторства Якуба Зеленки. Досі єдиним способом мультиплексування вводу-виводу в PHP був stream_select() з усіма його обмеженнями. Тепер зʼявився простір імен Io\Poll з класами Context, Event і StreamPollHandle:
use Io\Poll\Context;
use Io\Poll\Event;
use Io\Poll\StreamPollHandle;
$context = new Context();
$stream = stream_socket_server('tcp://0.0.0.0:8080', $errno, $errstr);
stream_set_blocking($stream, false);
$handle = new StreamPollHandle($stream);
$context->add($handle, [Event::Read], ['type' => 'server']);
while (true) {
$watchers = $context->wait(1);
// …
}
Два уточнення, щоб не було завищених очікувань. По-перше, це не асинхронність: жодних нових асинхронних можливостей PHP не отримує, це просто зручніший інтерфейс до того, що вже було. По-друге, вбудованого event loop немає - обгортка лишається на совісті бібліотек.
Реальний виграш тут не для прикладного коду, а для інфраструктурного: API вміє працювати з epoll і WSAPoll там, де вони доступні, тож на масштабі буде швидше за stream_select(). Найбільше з цього візьмуть ReactPHP та Amp, а всередині самого PHP - PHP-FPM і обробка сигналів у режимі ZTS.
Дрібниці, які знадобляться щодня
Вбудований clamp(). Функція, яку кожен фреймворк писав сам, тепер у ядрі. Сигнатура з RFC - clamp(mixed $value, mixed $min, mixed $max): mixed, межі включні:
clamp(10, min: 0, max: 100); // 10
clamp(101, min: 0, max: 100); // 100
clamp(-1, min: 0, max: 100); // 0
Працює не лише з числами, а з будь-якими порівнюваними значеннями - рядками і датами теж:
clamp("a", "x", "z"); // "x"
clamp(
value: new DateTimeImmutable('2025-01-01'),
min: new DateTimeImmutable('2026-01-01'),
max: new DateTimeImmutable('2026-12-31'),
); // DateTimeImmutable('2026-01-01')
Деталь, якої немає у більшості userland-реалізацій: якщо $min більший за $max або якась із меж дорівнює NAN, кидається ValueError. Тобто на відміну від саморобного тернарника, зіпсовані межі не проходять тихо.
Енум SortDirection з випадками Ascending і Descending. Поки що це просто спільний словник для бібліотек: вбудовані функції на кшталт array_multisort() чи scandir() його ще не приймають, це в планах.
Doc-коментарі до параметрів. ReflectionParameter отримав getDocComment(), тож типи можна писати біля самого параметра, а не в шапці функції:
function store(
/** @param Book[] */
array $books,
): void { /* … */ }
Пастка: якщо ставите коментар після параметра, він має бути перед комою, інакше не звʼяжеться. Найбільше з цього візьмуть статичні аналізатори.
__debugInfo() в енумах - тепер можна керувати тим, що показує var_dump() для енума. Плюс до рефлексії додали ReflectionProperty::isReadable() та isWriteable() з урахуванням області видимості, що стало доречним після появи хуків властивостей у PHP 8.4.
Що ще не вирішено
Одна річ зі списку новинок PHP 8.6 поки що не є доконаним фактом. RFC Readonly Property Defaults Ніка Сдота на момент написання має статус Voting, а не Implemented, тож розраховувати на цю можливість зарано.
Ідея слушна: після того як хуки властивостей дозволили описувати властивості в інтерфейсах, заборона на значення за замовчуванням для readonly почала заважати:
final class CreateBooksTable implements MigratesUp
{
public readonly string $name = '2026-01-01_create_books_table';
}
Спочатку цю заборону зробили свідомо - readonly-властивість зі значенням за замовчуванням фактично є константою. Але з інтерфейсними хуками незмінне значення стало частиною контракту, і аргумент проти ослаб. Голосування потребує 2/3 голосів; результат варто перевірити перед тим, як закладати це в код.
Депрекейти: чим доведеться зайнятися
Пакет депрекейтів для PHP 8.6 - понад двадцять пунктів. Більшість чиститься механічно, і зробити це варто зараз, а не тоді, коли вони перетворяться на помилки в PHP 9.
Проста заміна один-в-один:
is_double(), is_integer(), is_long() - на is_int() та is_float();
doubleval() - на floatval();
spl_object_hash() - на spl_object_id();
spl_classes() - на ReflectionExtension::getClassNames();
$mysqli->stmt_init() - на $mysqli->prepare();
strcoll(), metaphone() і прапорець SORT_LOCALE_STRING.
Вимагає уваги, а не лише sed:
return у блоці finally - класичне джерело непомітних багів, коли повернення з finally тихо перекриває результат try;
- CSV-методи
SplFileObject (fgetcsv(), fputcsv(), setCsvControl(), getCsvControl()) - обробка CSV поїде в окреме розширення;
- десять методів
ArrayIterator (asort(), ksort(), serialize() та інші), які існували лише через спільну колись реалізацію з ArrayObject;
return у конструкторах і деструкторах;
is_subclass_of() та is_a() з allow_string: false - у такій комбінації рядок завжди давав false, тобто передавання рядка означало баг.
Окремо - зарезервовані слова: let, is, namespace як імʼя константи класу, функція з іменем readonly і константа _. Ці депрекейти зроблені під майбутній синтаксис, зокрема is тримають під можливий match.
Дефолти сесій: єдине місце, де може боляче
Це найважливіша практична зміна релізу, бо вона зачіпає застосунки, які взагалі нічого не міняли в коді. Три ini-налаштування отримали нові значення за замовчуванням:
session.use_strict_mode - тепер 1 замість 0;
session.cookie_httponly - тепер 1 замість 0;
session.cookie_samesite - тепер Lax замість порожнього значення.
Хто це відчує:
- Передавання ID сесії ззовні (наприклад, обмін між піддоменами через save handler
files) перестане працювати: зі строгим режимом ID без наявного файлу сесії відхиляється. Правильний шлях - записати й закрити сесію через session_write_close() на боці-джерелі до передавання ID. Власні обробники, чий validateId() завжди повертає true, змін не помітять - сюди потрапляють і стандартні обробники Redis та Memcached, які цей метод не реалізують.
- Читання cookie сесії з JavaScript через
document.cookie більше не спрацює. Якщо клієнтському коду потрібен токен для звірки запитів, це має бути окремий не-HttpOnly CSRF-токен, а не сам ID сесії.
- Міждоменні POST-запити з cookie сесії - SP-ініційований SAML SSO, застарілі міждоменні форми. З
SameSite=Lax cookie не їде на міждоменний POST, тож для таких ендпоінтів доведеться явно виставляти SameSite=None; Secure або переходити на токени.
Пом'якшує ситуацію те, що Chrome і Firefox уже давно застосовують Lax як неявний дефолт для cookie без атрибута SameSite. Тобто більшість застосунків уже живе за цими правилами, просто тепер поведінка стає однаковою в усіх браузерах і версіях PHP.
Що робити зараз
До релізу три з половиною місяці, і найкорисніше - не чекати листопада, а прогнати депрекейти. Більшість із них ловить статичний аналіз: PHPStan і Rector дадуть список замін is_integer(), spl_object_hash() і подібного за один прохід, і це можна злити одним рутинним PR без ризику.
Окремо варто перевірити сесії - це єдине, що може зламатися без жодних змін у вашому коді. Якщо у вас є SAML SSO, міждоменні форми або обмін сесією між піддоменами, поставте потрібні значення session.* явно в php.ini вже зараз: тоді оновлення на 8.6 нічого не змінить, бо ви більше не покладаєтеся на дефолти.
А от закладати в код часткове застосування функцій ще рано - воно з'явиться лише в 8.6, і код із ? просто не розпарситься на 8.5. Те саме стосується значень за замовчуванням для readonly: там ще й голосування не завершене.