PHP 8.6 виходить у листопаді, і що в ньому буде, ми вже розбирали. Тим часом на сторінці RFC з'являються пропозиції для наступної версії, PHP 8.7. Нижче - те, що потрапило туди в жовтні 2026 року і може торкнутися щоденного коду. Важливо: усе описане - пропозиції. Два RFC уже на голосуванні, решта ще обговорюється, і будь-яку з них можуть змінити або відхилити. Стан голосувань наведено на 28 жовтня.
На голосуванні: винятки для hexdec() і base_convert()
Функції bindec(), octdec(), hexdec() і base_convert() досі мовчки ігнорують символи, яких немає в системі числення. hexdec('z') повертає 0, а з PHP 7.4 ще й показує E_DEPRECATED. Тоді ж планували в PHP 8 перейти до винятку, але цей крок так і не зробили. RFC Сьорда Лангкемпера його завершує:
var_dump(hexdec('z'));
// PHP 7.4 - 8.6: Deprecated + int(0)
// PHP 8.7 (пропозиція): Uncaught Exception: Invalid characters passed for attempted conversion
base_convert('f', 16, 10); // гаразд, 'f' є в шістнадцятковій системі
base_convert('f', 15, 10); // Exception
Зверніть увагу на тип: це звичайний Exception, а не ValueError. Автор пояснює, що невалідний символ на вході не обов'язково означає баг у програмі, тож Error тут не пасує. Більш специфічний виняток лишили на майбутнє.
Це справжній BC break: код, що покладається на мовчазне відкидання символів (наприклад, hexdec('1234-5678')), почне падати. Голосування триває до 1 листопада, потрібна більшість у 2/3. На 28 жовтня рахунок 24 «за» і 1 «проти», тож прийняття виглядає майже певним.
На голосуванні: енуми Filter\Filter і Filter\Flag
Другий RFC пропонує типізовану альтернативу цілочисловим константам FILTER_*: backed-енуми Filter\Filter для фільтрів і Filter\Flag для прапорців. Функції filter_var(), filter_var_array(), filter_input() і filter_input_array() прийматимуть і їх, і старі константи:
filter_var('123', Filter\Filter::VALIDATE_INT, Filter\Flag::ALLOW_OCTAL);
// кілька прапорців - через їхні значення
filter_var(
'123',
Filter\Filter::VALIDATE_INT,
Filter\Flag::ALLOW_OCTAL->value | Filter\Flag::ALLOW_HEX->value
);
BC break немає: константи лишаються. Але шанси в RFC слабкі. Голосування триває до 4 листопада, і на 28 жовтня рахунок 2 «за» і 11 «проти» при потрібних 2/3.
Коментарі й висячі коми в JSON
RFC Отара Чекурішвілі додає два незалежні прапорці для json_decode() і json_validate(): JSON_ALLOW_COMMENTS (коментарі // і /* */, але не #) і JSON_ALLOW_TRAILING_COMMAS (одна кома після останнього елемента масиву чи об'єкта):
$config = <<<'JSON'
{
// підключення для локальної розробки
"host": "localhost",
"port": 5432, /* стандартний порт PostgreSQL */
"options": ["persistent", "utf8",],
}
JSON;
$data = json_decode($config, true, flags: JSON_ALLOW_COMMENTS | JSON_ALLOW_TRAILING_COMMAS);
Без прапорців поведінка не змінюється, а json_encode() коментарів і ком ніколи не пише. Це зручно для конфігів, які редагують люди: зараз для цього тягнуть окрему бібліотеку або вирізають коментарі регулярками. Голосування буде окремим для кожного прапорця.
ext/csv на заміну SplFileObject
PHP 8.6 задепрекейтив CSV-методи SplFileObject, але заміни не запропонував. RFC про ext/csv на основі розширення girgias/csv Джини Пітер Баньярд додає простір імен Csv\: функції array_to_row(), row_to_array(), collection_to_buffer(), buffer_to_collection(), buffer_to_collection_lax(), collection_to_file() і клас LazyLaxCollection:
Csv\collection_to_file($file, $rows);
foreach (Csv\LazyLaxCollection::createFromFile($file) as $row) {
echo implode(' | ', $row), "\n";
}
На відміну від fgetcsv(), тут немає власного механізму екранування, розбір не залежить від локалі, а роздільники можуть бути багатобайтовими. Парсер строго дотримується RFC 4180 і на некоректному вході кидає ValueError замість здогадок. Одна деталь для імпорту файлів: за замовчуванням кінець рядка - "\r\n", і файл лише з LF треба читати з явним "\n", інакше буде помилка.
array_partition() і PASSWORD_BCRYPT_SHA256
array_partition() ділить масив на дві частини за колбеком - те, що Laravel-розробники давно роблять через Collection::partition(). Колбек отримує значення і ключ, ключі зберігаються, першою йде частина, де колбек повернув істину:
[$adults, $minors] = array_partition($users, fn($u) => $u->age >= 18);
Якщо у вашому коді є власна глобальна array_partition() без перевірки function_exists(), після прийняття RFC буде помилка «Cannot redeclare function».
PASSWORD_BCRYPT_SHA256 - новий алгоритм для password_hash(). Звичайний bcrypt мовчки обрізає пароль на 72 байтах, а пропозиція спершу пропускає його через HMAC-SHA256. Формат узято з Python-бібліотеки Passlib, тож хеші сумісні в обидва боки. PASSWORD_DEFAULT і PASSWORD_BCRYPT не змінюються, алгоритм вмикається явно:
$hash = password_hash($password, PASSWORD_BCRYPT_SHA256);
password_verify($password, $hash); // true
Laravel за замовчуванням хешує паролі через bcrypt, тож тема знайома. Щоб скористатися новим алгоритмом через Hash, знадобиться підтримка з боку фреймворку - про неї поки нічого не відомо.
Єдиний бінарник для CLI і FPM та клас Scope
Два RFC стосуються не так коду, як того, де він працює. Пропозиція Матьє Наполі, автора Bref, вбудовує CLI у виконуваний файл php-fpm: викликаний під ім'ям php (наприклад, через симлінк), він працює як CLI. За його вимірами на AWS Lambda розпакований шар зменшується на 28%, а холодний старт із FPM пришвидшується на 70 мс. Для Docker-образів, де CLI запускає міграції перед стартом FPM, це теж може бути відчутно.
RFC Scope Фроде Бьорлі пропонує фінальний клас, який позначає одиницю роботи: HTTP-запит, завдання з черги, CLI-команду. Сервер запускає кожен запит у власному scope, а код будь-де всередині може спитати, до якого запиту він належить:
(new Scope())->run($handler, $request);
$this->connections ??= new WeakMap();
$db = $this->connections[Scope::getCurrent()] ??= new PDO($dsn);
Файбери й генератори успадковують scope, у якому їх створено. Два додаткові голосування пропонують прив'язати до scope суперглобальні змінні й сесію, а також локаль і часовий пояс за замовчуванням. Для Octane і FrankenPHP, де один процес обслуговує багато запитів, це саме та проблема, яку зараз розв'язують скиданням стану між запитами. Глобальний клас Scope більше не можна буде оголосити, але скопи Eloquent (Illuminate\Database\Eloquent\Scope) живуть у своєму просторі імен і не постраждають. Обговорення почалося лише 26 жовтня, тож RFC ще точно змінюватиметься.
Що робити вже зараз
Реально підготуватися варто лише до першого RFC. Пошукайте в логах E_DEPRECATED з текстом «Invalid characters passed for attempted conversion» і подивіться, чи не передаєте ви у hexdec() чи base_convert() дані користувача: кольори, ідентифікатори, рядки з роздільниками. Такі місця краще валідувати до виклику, ніж ловити Exception.
Решта - пропозиції в обговоренні: дати голосувань ще не призначені, а тексти змінюються мало не щотижня. Якщо одна з них вам болить (CSV-імпорт, довгі паролі, стан у довгоживучих воркерах), найкраще місце для відгуку - розсилка internals, поки RFC не дійшли до голосування.