---
title: "PHP 8.6: що готує листопадовий реліз"
url: https://laravelukraine.com/blog/php-86-shho-gotuje-listopadovii-reliz
author: "Олексій Бабінцев"
date: 2026-08-05
---

# PHP 8.6: що готує листопадовий реліз

PHP 8.6 виходить 19 листопада 2026 року. Feature freeze ще попереду, але основне вже у гілці, і цього разу найцікавіше - не окрема можливість, а те, як дві з них нарешті складаються докупи. Детальний огляд змін зібрав [Брент Роозе](https://stitcher.io/blog/new-in-php-86); нижче - те, що з цього справді варто знати, звірене з відповідними RFC.

### Часткове застосування функцій рятує оператор |>

[Оператор `|>`](https://wiki.php.net/rfc/pipe-operator-v3) приїхав ще в PHP 8.5, але користуватися ним було незручно: він вимагає callable рівно з одним параметром. Тобто `strtolower(...)` у пайплайн стає, а `str_replace(' ', '-', ...)` - ні, бо в неї три параметри. На практиці це означало замикання вручну на кожному другому кроці, і половина краси пайплайна зникала.

[Часткове застосування функцій](https://wiki.php.net/rfc/partial_function_application_v2) (partial function application, PFA) закриває саме цю дірку. Знак `?` позначає параметр, який лишається незаповненим, а решта фіксується одразу:

```php
$makeSlug = str_replace(' ', '-', ?);

$makeSlug('Hello World');
// Hello-World
```

Результат - звичайне замикання, яке можна передати куди завгодно. Але найкраще воно виглядає саме в парі з `|>`:

```php
$output = 'Hello World'
  |> str_replace(' ', '-', ?)
  |> strtolower(...);

// hello-world
```

Автори RFC - Ларрі Гарфілд і Арно ле Блан, статус - Implemented. Якщо ви дивилися на пайплайн-оператор і не розуміли, навіщо він потрібен, то ось відповідь: без PFA він був напівфабрикатом.

### Io\Poll: заміна stream_select

Друга велика річ - [Polling API](https://wiki.php.net/rfc/poll_api) авторства Якуба Зеленки. Досі єдиним способом мультиплексування вводу-виводу в PHP був `stream_select()` з усіма його обмеженнями. Тепер зʼявився простір імен `Io\Poll` з класами `Context`, `Event` і `StreamPollHandle`:

```php
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](https://wiki.php.net/rfc/clamp_v2) - `clamp(mixed $value, mixed $min, mixed $max): mixed`, межі включні:

```php
clamp(10, min: 0, max: 100);  // 10
clamp(101, min: 0, max: 100); // 100
clamp(-1, min: 0, max: 100);  // 0
```

Працює не лише з числами, а з будь-якими порівнюваними значеннями - рядками і датами теж:

```php
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()`, тож типи можна писати біля самого параметра, а не в шапці функції:

```php
function store(
  /** @param Book[] */
  array $books,
): void { /* … */ }
```

Пастка: якщо ставите коментар після параметра, він має бути перед комою, інакше не звʼяжеться. Найбільше з цього візьмуть статичні аналізатори.

**`__debugInfo()` в енумах** - тепер можна керувати тим, що показує `var_dump()` для енума. Плюс до рефлексії додали `ReflectionProperty::isReadable()` та `isWriteable()` з урахуванням області видимості, що стало доречним після появи хуків властивостей у PHP 8.4.

### Що ще не вирішено

Одна річ зі списку новинок PHP 8.6 поки що не є доконаним фактом. RFC [Readonly Property Defaults](https://wiki.php.net/rfc/readonly_property_defaults) Ніка Сдота на момент написання має статус **Voting**, а не Implemented, тож розраховувати на цю можливість зарано.

Ідея слушна: після того як хуки властивостей дозволили описувати властивості в інтерфейсах, заборона на значення за замовчуванням для `readonly` почала заважати:

```php
final class CreateBooksTable implements MigratesUp
{
  public readonly string $name = '2026-01-01_create_books_table';
}
```

Спочатку цю заборону зробили свідомо - readonly-властивість зі значенням за замовчуванням фактично є константою. Але з інтерфейсними хуками незмінне значення стало частиною контракту, і аргумент проти ослаб. Голосування потребує 2/3 голосів; результат варто перевірити перед тим, як закладати це в код.

### Депрекейти: чим доведеться зайнятися

[Пакет депрекейтів для PHP 8.6](https://wiki.php.net/rfc/deprecations_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`: там ще й голосування не завершене.
