0Pricing
PHP Academy · Урок

Циклы событий с ReactPHP

Выполняйте неблокирующий ввод-вывод с помощью цикла событий ReactPHP

«Циклы событий с ReactPHP» — бесплатный урок PHP Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения PHP Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс PHP Academy содержит 4 уроков всего.

ReactPHP в одном предложении

ReactPHP — это набор инструментов на чистом PHP без зависимостей для неблокирующего ввода-вывода, построенный вокруг одного цикла событий. Расширение не требуется — всё работает на стандартном PHP с использованием stream_select (или ext-event/ext-ev, если они доступны). С его помощью вы пишете на обычном PHP долго работающие серверы, параллельные HTTP-клиенты и конвейеры потоковой обработки.

Глобальный цикл

Современный ReactPHP (v1.2+) предоставляет цикл по умолчанию через статический фасад React\EventLoop\Loop — больше не нужно повсюду передавать экземпляр цикла. Запланируйте обратный вызов с помощью таймера; цикл работает, пока не останется работы.

<?php
require 'vendor/autoload.php';
use React\EventLoop\Loop;

Loop::addTimer(1.0, function () {
    echo "fired after 1 second\n";
});
echo "scheduled\n";
// Loop::run() is auto-invoked at script shutdown in v1.2+

Таймеры и периодическая работа

Неблокирующее управление временем заменяет sleep(). addTimer срабатывает один раз, а addPeriodicTimer — повторно. Отмените любой из них, чтобы остановить его. Поскольку ими управляет цикл, другие задачи продолжают выполняться, пока таймер ожидает.

<?php
use React\EventLoop\Loop;

$count = 0;
$timer = Loop::addPeriodicTimer(0.5, function () use (&$count, &$timer) {
    echo 'tick ' . (++$count) . "\n";
    if ($count === 4) {
        Loop::cancelTimer($timer); // stop after 4 ticks
    }
});

Обещания для асинхронных результатов

ReactPHP возвращает обещание для любой операции, которая завершится позже. Добавьте then() для успешного результата и второй обратный вызов для ошибки. Обещания можно компоновать, поэтому вы можете преобразовывать результаты и объединять их в цепочки без глубокой вложенности обратных вызовов.

<?php
use React\Promise\Promise;

function asyncDouble(int $n): Promise {
    return new Promise(function ($resolve) use ($n) {
        \React\EventLoop\Loop::addTimer(0.1, fn() => $resolve($n * 2));
    });
}

asyncDouble(21)
    ->then(fn($r) => printf("result: %d\n", $r))   // 42
    ->catch(fn(\Throwable $e) => printf("err: %s\n", $e->getMessage()));

Параллельные HTTP-запросы с асинхронным клиентом

Главное преимущество — отправка множества HTTP-запросов одновременно из одного потока. React\Http\Browser возвращает обещания, а React\Promise\all() ожидает завершения всей группы. Десять запросов длительностью в одну секунду завершаются примерно за одну секунду, а не за десять.

<?php
use React\Http\Browser;
use function React\Promise\all;

$browser = new Browser();
$urls = [
    'https://httpbin.org/delay/1',
    'https://httpbin.org/delay/1',
    'https://httpbin.org/delay/1',
];
$requests = array_map(fn($u) => $browser->get($u), $urls);

all($requests)->then(function (array $responses) {
    echo 'all done: ' . count($responses) . " responses\n";
});

Волокна делают код синхронно читаемым

Цепочки обратных вызовов становятся громоздкими. ReactPHP интегрируется с react/async, которое использует волокна PHP 8.1, чтобы вы могли выполнить await() для обещания и писать последовательный код. Обёртка coroutine()/async() запускает вашу функцию внутри волокна.

<?php
use function React\Async\async;
use function React\Async\await;
use React\Http\Browser;

$main = async(function () {
    $browser = new Browser();
    $a = await($browser->get('https://httpbin.org/uuid'));
    $b = await($browser->get('https://httpbin.org/uuid'));
    echo (string) $a->getBody();
    echo (string) $b->getBody();
});
$main();

Неблокирующий TCP-сервер

ReactPHP особенно хорошо подходит для серверов. SocketServer принимает соединения и создаёт события; каждое соединение само является неблокирующим потоком. Один процесс обслуживает тысячи одновременно подключённых клиентов.

<?php
use React\Socket\SocketServer;
use React\Socket\ConnectionInterface;

$socket = new SocketServer('127.0.0.1:8080');
$socket->on('connection', function (ConnectionInterface $conn) {
    $conn->write("Welcome\n");
    $conn->on('data', function ($data) use ($conn) {
        $conn->write('echo: ' . $data); // streamed back, non-blocking
    });
});
echo "listening on 127.0.0.1:8080\n";

HTTP-сервер в несколько строк

Сервер react/http обрабатывает запросы с помощью обратного вызова, возвращающего Response (или обещание такого результата). Поскольку это постоянно работающий процесс, зависимости инициализируются один раз — но и управление памятью между запросами также становится вашей ответственностью.

<?php
use React\Http\HttpServer;
use React\Http\Message\Response;
use Psr\Http\Message\ServerRequestInterface;
use React\Socket\SocketServer;

$http = new HttpServer(function (ServerRequestInterface $request) {
    return Response::json(['path' => $request->getUri()->getPath()]);
});
$http->listen(new SocketServer('0.0.0.0:8000'));
echo "http://0.0.0.0:8000\n";

Потоковая передача без буферизации

Потоки ReactPHP реализуют обратное давление: вы можете передать pipe() читаемый поток в записываемый, а среда выполнения автоматически ограничит скорость. Это позволяет перемещать гигабайты, используя постоянный небольшой объём памяти, вместо загрузки файлов в RAM.

<?php
use React\Stream\ReadableResourceStream;
use React\Stream\WritableResourceStream;

$source = new ReadableResourceStream(fopen('big.log', 'r'));
$dest   = new WritableResourceStream(fopen('php://stdout', 'w'));
$source->pipe($dest); // back-pressure aware, never buffers whole file

Главный смертный грех: блокировка цикла

Всё ломается, как только вы вызываете блокирующую функцию внутри обработчика — sleep(), блокирующий PDO, curl_exec, file_get_contents для получения данных по адресу. Они замораживают единственный поток и останавливают все соединения. Используйте асинхронные эквиваленты React: Loop::addTimer для задержек, clue/reactphp-* или асинхронные драйверы базы данных для запросов, а также Browser для HTTP.

<?php
use React\EventLoop\Loop;
use function React\Async\await;
use React\Promise\Deferred;

// BAD:  sleep(2);  // freezes the entire loop
// GOOD: async delay that yields control
function delay(float $s): \React\Promise\PromiseInterface {
    $d = new Deferred();
    Loop::addTimer($s, fn() => $d->resolve(null));
    return $d->promise();
}
// await(delay(2.0)); // inside a fiber/coroutine

Ограничение конкурентности

Неограниченное разветвление запросов может исчерпать файловые дескрипторы или перегрузить внешний API. clue/mq-react от ReactPHP ставит предел конкурентности при обработке очереди: одновременно обрабатывается N обещаний, а остальные ожидают. Это создаёт обратное давление на ваши исходящие запросы.

<?php
use Clue\React\Mq\Queue;
use React\Http\Browser;

$browser = new Browser();
// At most 10 in-flight requests across 1000 URLs
$q = new Queue(10, null, fn($url) => $browser->get($url));

$promises = array_map(fn($u) => $q($u), $thousandUrls ?? []);
\React\Promise\all($promises)->then(function ($responses) {
    echo 'completed ' . count($responses) . " with cap of 10\n";
});

Быстрая проверка

Как приостановить выполнение на 2 секунды внутри обработчика ReactPHP, не нарушив конкурентность?

Повторение

ReactPHP добавляет неблокирующий ввод-вывод в обычный PHP:

  • Единый цикл событий (фасад Loop) управляет таймерами, сокетами и потоками — расширение не требуется.
  • Операции возвращают промисы; all() запускает запросы параллельно, а react/async и Fiber позволяют использовать await(), сохраняя синхронный вид кода.
  • Создавайте постоянные TCP/HTTP-серверы и потоки с контролем обратного давления, обрабатывающие тысячи соединений в одном процессе.
  • Никогда не блокируйте цикл — заменяйте sleep и блокирующий ввод-вывод таймерами React и асинхронными драйверами.

Часто задаваемые вопросы

Урок «Циклы событий с ReactPHP» бесплатный?

Да — полный текст урока «Циклы событий с ReactPHP» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс PHP Academy, подпишись на CoddyKit PRO. Курс PHP Academy содержит 4 уроков всего.

Чему я научусь в уроке «Циклы событий с ReactPHP»?

Выполняйте неблокирующий ввод-вывод с помощью цикла событий ReactPHP Ты практикуешь PHP Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать PHP Academy?

Предыдущий опыт не требуется. PHP Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Циклы событий с ReactPHP»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке PHP Academy?

Да. Каждый урок PHP Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Модель конкурентности PHP
  2. Волокна PHP 8.1
  3. Циклы событий с ReactPHP
  4. Высокопроизводительные серверы со Swoole
← Назад к PHP Academy