Polling API — найбільш недооцінений RFC у PHP за останні роки

Перекладено ШІ 0 JustSteveKing 05 серпня, 2026

PHP 8.6 отримає Polling API, який нативно впроваджує сучасні epoll та kqueue для роботи з високими навантаженнями. Дізнайтеся, чому цей тихий фундамент для асинхронності є найважливішою зміною в мові за останні роки.

Поки більшість спільноти навесні сперечалася про generics, до PHP 8.6 непомітно потрапив інший RFC, якому приділили критично мало уваги. Жодних довгих тредів у Twitter чи розлогих постів у блогах. Лише тихе голосування, що завершилося третього червня: 33 голоси «за», один «проти» та четверо тих, хто утримався. Серед підписантів — автор Composer, творець FrankenPHP та значна частина розробників, які підтримують async-бібліотеки, якими ви користуєтеся.

Цей RFC — Polling API, створений Jakub Zelenka у межах його роботи над еволюцією stream. Я впевнений, що це найважливіша подія в PHP за останні роки. А те, що про неї ніхто не говорить, лише підтверджує її значущість.

Проблема, яку ви звикли обходити стороною

Якщо ви хоч раз писали код, що має відстежувати більше одного сокета одночасно, ви стикалися зі stream_select(). Це чи не єдиний примітив для I/O multiplexing, який PHP пропонував майже всю свою історію. Він базується на системному виклику select() зразка 1983 року. Він працює, але має обмеження, які кожному автору async-бібліотек доводилося героїчно долати.

По-перше, це ліміт файлових дескрипторів. Поточна імплементація на більшості систем обмежена приблизно 1024 дескрипторами. Це окей, поки ви не впретеся в цю межу. По-друге — складність. select() має складність O(n): при кожному виклику він сканує кожен дескриптор, тому продуктивність падає зі зростанням кількості з'єднань. По-третє, він не дає доступу до механізмів, якими решта світу користується вже два десятиліття. Тут немає epoll для Linux, kqueue для BSD чи macOS, або event ports для Solaris. Немає ні edge-triggered, ні one-shot режимів.

Саме тому будь-який серйозний async runtime ігнорує select(). Node, Nginx, пакет net у Go, Tokio в Rust — усі вони базуються на epoll або kqueue, бо це фундамент для високонавантажених мережевих рішень. PHP залишався останнім великим рантаймом без нативної підтримки цих технологій. Через це AMPHP та ReactPHP роками постачали кілька драйверів: StreamSelect для загальних випадків та окремий драйвер libuv для тих, хто встановив ext-uv. Рядок «встановіть ext-uv для швидкості» у README — це лише милиця для закриття дірки в самій мові.

Polling API нарешті закриває цю дірку.

Чим це є насправді (і чим не є)

Пропозиція додає невеликий набір класів у новий простір імен Io\Poll. Система автоматично обирає найкращий бекенд для вашої платформи та надає уніфікований інтерфейс. На Linux ви отримуєте epoll, на macOS та BSD — kqueue, на Solaris та illumos — event ports, на Windows — WSAPoll, а для всього іншого залишається poll(). Вам не потрібно про це думати: ви створюєте context, а він сам обирає потрібний механізм.

Ось як це виглядає: ви створюєте Context, обгортаєте потік у StreamPollHandle, додаєте його до контексту з подіями, які вас цікавлять, і викликаєте wait():

<?php

declare(strict_types=1);

use Io\Poll\{Context, Event};

$poll = new Context();

$server = stream_socket_server('tcp://0.0.0.0:8080', $errno, $errstr);
stream_set_blocking($server, false);

$poll->add(new StreamPollHandle($server), [Event::Read], ['type' => 'server']);

while (true) {
    foreach ($poll->wait(timeoutSeconds: 1) as $watcher) {
        // watcher повертається лише тоді, коли подія готова
        $handle = $watcher->getHandle();

        if ($watcher->hasTriggered(Event::Read)) {
            // accept, read, write — що завгодно для цього дескриптора
        }
    }
}

Це і є основа. Метод wait() блокує виконання, поки щось не станеться або не вийде час очікування, а потім повертає лише ті watchers, що спрацювали. Не треба власноруч сканувати весь масив дескрипторів чи вгадувати, який саме сокет прокинувся.

Важливо розуміти, чого в RFC свідомо немає. Це не event loop. У першій ітерації немає таймерів, обробки сигналів чи керування дочірніми процесами. Це лише примітив: спосіб ефективно стежити за файловими дескрипторами, використовуючи найкращі можливості ОС. Якщо вам потрібен повноцінний event loop, ви все одно оберете AMPHP чи ReactPHP. Різниця в тому, що тепер ці бібліотеки зможуть базуватися на єдиному нативному бекенді замість підтримки власних.

Те, про що всі мовчать

Мені здається, що більшість оглядів упустили справжню суть. Усі описують це як функцію для userspace — швидший stream_select() для розробників WebSocket-серверів. Це справді круто, але це лише приємний бонус.

Якщо уважно прочитати RFC, головна мотивація — це внутрішній API. Jakub будує уніфікований інтерфейс опитування, який зможуть використовувати ядро PHP та розширення. Класи для користувачів — це лише наслідок якісно виконаної роботи. Внутрішній заголовок php_poll.h — ось де справжня цінність.

Чому це важливо для вас, якщо ви пишете прикладний код і навряд чи коли-небудь напишете new Context()? Тому що це розв'язує руки на низькому рівні. Це безпечна та ефективна обробка сигналів у ZTS, яка критично необхідна FrankenPHP для роботи з потоками на базі goroutine. Це гнучкіша обробка подій у воркерах PHP-FPM замість нинішніх ad-hoc рішень. Це кросплатформний фундамент для таймерів, з якими на macOS завжди були проблеми. Це стандартний інтерфейс, на який зможуть перейти розширення sockets та curl замість того, щоб винаходити власні велосипеди.

Розділ «Future scope» виглядає як дорожня карта мережевих нутрощів PHP на найближчі роки: SocketPollHandle, CurlPollHandle, TimerHandle, SignalHandle, міграція FPM на внутрішній цикл та консолідація розрізненого коду в одному місці. У цьому немає лоску, але це та фундаментальна інженерія, яка піднімає планку для всього, що будується зверху.

Найкращі зміни в інфраструктурі — непомітні. Ви не помічаєте epoll. Ви просто бачите, що Nginx легко тримає десять тисяч з'єднань, і не замислюєтеся, чому.

Архітектурні рішення, варті уваги

Кілька рішень в API заслуговують на окрему увагу, бо вони демонструють глибину пропрацювання.

Перше — інтерфейс Handle. Це маркерний інтерфейс без методів, який неможливо реалізувати в коді користувача. Якщо спробуєте, отримаєте Fatal error ще під час оголошення класу:

Fatal error: Io\Poll\Handle cannot be implemented by user classes

Це виглядає як обмеження, поки не зрозумієш причину. Кожен конкретний тип handle має зареєструвати таблицю операцій на рівні C (php_poll_handle_ops), яка пояснює бекенду, як отримати дескриптор і перевірити його валідність. Користувацький клас цього зробити не може. Обмежуючи інтерфейс внутрішніми класами, мова гарантує: будь-який Handle, що потрапив у бекенд, точно має робочу таблицю операцій. Якщо вам треба опитати кастомний ресурс, ви обгортаєте його в StreamPollHandle. Чистий інтерфейс для користувача, вся брудна логіка дескрипторів — на боці C.

Друге — Watcher. Ви не створюєте його напряму; Context::add() повертає його вам. Він містить усе про реєстрацію: які події відстежуються, які спрацювали, та довільні дані користувача. Його можна змінювати на льоту або видаляти:

<?php

declare(strict_types=1);

use Io\Poll\{Context, Event};

$poll = new Context();
$stream = fopen('php://temp', 'r+');

$watcher = $poll->add(new StreamPollHandle($stream), [Event::Read], 'some data');

// Зміна подій без перереєстрації
$watcher->modifyEvents([Event::Write]);

// Або зміна подій і даних одночасно
$watcher->modify([Event::Read, Event::Write], 'updated data');

if ($watcher->isActive()) {
    $watcher->remove();
}

Enum Event — це місце, де проявляються сучасні механізми. Разом із Read та Write ви отримуєте Event::OneShot (watcher автоматично видаляється після спрацьовування) та Event::EdgeTriggered для високопродуктивного режиму, що повідомляє лише про зміну стану. Саме edge triggering дозволяє epoll та kqueue масштабуватися до десятків тисяч з'єднань. Тепер це доступно через один елемент enum.

Наратив, який це тихо вбиває

Ви чули це тисячу разів, зазвичай від тих, хто востаннє бачив PHP у 2014-му: «PHP не масштабується». Довгий час у цьому була частка технічної правди, бо мова не давала нативного доступу до примітивів опитування, на яких будуються високонавантажені сервери. Можна було використати сторонні бібліотеки та розширення, але в самому фундаменті мови цього не було.

З версією 8.6 ситуація змінюється. AMPHP, ReactPHP та Revolt зможуть перенести свої бекенди на цей API. Примітка про ext-uv почне зникати з інструкцій. Бенчмарки, що демонструють вирішення проблеми C10K на чистому PHP, стануть нормою, бо там, де select() здається після кількох сотень з'єднань, epoll та kqueue впевнено тримають десять тисяч і більше. Останній технічний аргумент на користь того, що «PHP не масштабується», відпадає. Залишається лише інерція сприйняття.

Будемо чесними: цей RFC пройшов не одностайно, були й ті, хто утримався. Більшість розробників ніколи не напишуть new Io\Poll\Context(). Але ви відчуєте це через свій фреймворк, через швидший FPM, через async-бібліотеки, що нарешті матимуть один нативний бекенд замість трьох, та через FrankenPHP. Користь реальна, хоча вона майже повністю опосередкована.

Саме тому ця подія недооцінена. Гучні фічі привертають увагу автоматично. Тихий фундамент, що піднімає рівень усієї екосистеми, потребує роз'яснень, бо люди, яким він допомагає найбільше, можуть ніколи його не побачити. Jakub виконав невдячну роботу, створивши примітив, якого не вистачало двадцять років, а професіонали, які розуміють його цінність, просто проголосували за нього без зайвого галасу.

Іноді найважливіше в релізі — це рядок, про який ніхто не сперечається.

Наступного разу, коли побачите пораду встановити ext-uv для швидкості, згадайте: у цієї поради тепер є термін придатності. Час подивитися, що робить Io\Poll під капотом.

Популярні

Інше, що варто прочитати

17 Оновлено 26 червня, 2026

Обробка геопросторових даних за допомогою Laravel Magellan

Ви готові відкрити нові горизонти у роботі з геопросторовими даними в Laravel? Дізнайтеся, як за допомогою PostGIS та пакету Laravel-Magellan можна легко зберігати, запитувати та маніпулювати інформацією про розташування, перетворюючи ваші проекти на вражаючі рішення у сфері картографії та геолокації!

32 Оновлено 26 червня, 2026

Інтеграція Laravel Socialite з бібліотекою Google Client PHP

Ви хочете навчитися, як інтегрувати Google OAuth у вашому проекті Laravel, використовуючи Socialite? Дізнайтеся, як налаштувати доступ до сервісів Google, таких як Календар, у нашій сьогоднішній статті

79 Оновлено 26 червня, 2026

Laravel Boost — ваш стартовий набір для програмування з використанням штучного інтелекту

Вперше у світі Laravel з'являється можливість, яка значно спростить ваше повсякденне програмування завдяки новому пакету Laravel Boost. Читайте статтю, щоб дізнатися, як посилена інтеграція штучного інтелекту може підвищити ефективність вашої роботи та оптимізувати створення проектів у Laravel