0Pricing
PHP Academy · Lekcja

Pętle zdarzeń z ReactPHP

Proszę uruchamiać nieblokujące operacje wejścia-wyjścia za pomocą pętli zdarzeń ReactPHP.

Pętle zdarzeń z ReactPHP to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.

ReactPHP w jednym zdaniu

ReactPHP to napisany wyłącznie w PHP, niewymagający zależności zestaw narzędzi do nieblokujących operacji wejścia-wyjścia, oparty na pojedynczej pętli zdarzeń. Nie wymaga żadnego rozszerzenia — działa na standardowym PHP, korzystając z stream_select (lub z ext-event/ext-ev, gdy są dostępne). Umożliwia tworzenie długo działających serwerów, równoległych klientów HTTP i potoków strumieniowania w zwykłym PHP.

Globalna pętla

Współczesny ReactPHP (v1.2+) udostępnia domyślną pętlę za pośrednictwem statycznej fasady React\EventLoop\Loop — nie trzeba wszędzie przekazywać instancji pętli. Callback można zaplanować za pomocą timera; pętla działa, dopóki ma jeszcze jakieś zadania.

<?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+

Timery i praca okresowa

Nieblokujące odmierzanie czasu zastępuje sleep(). addTimer uruchamia callback jednokrotnie, a addPeriodicTimer — cyklicznie. Każdy z nich można anulować, aby go zatrzymać. Ponieważ ich działanie kontroluje pętla, inne zadania mogą wykonywać się podczas oczekiwania timera.

<?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
    }
});

Promisy dla wyników asynchronicznych

ReactPHP zwraca promis dla każdej operacji, która zakończy się później. Do sukcesu służy then(), a do niepowodzenia — drugi callback. Promisy można komponować, dzięki czemu wyniki można przekształcać i łączyć bez głębokiego zagnieżdżania callbacków.

<?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()));

Równoległe żądania HTTP z klientem asynchronicznym

Największa korzyść: wysyłanie wielu żądań HTTP współbieżnie z jednego wątku. React\Http\Browser zwraca promisy, a React\Promise\all() czeka na całą partię. Dziesięć żądań trwających po 1 sekundzie kończy się w około 1 sekundę, a nie w 10.

<?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";
});

Fibers umożliwiają synchroniczny zapis

Łańcuchy callbacków stają się nieporęczne. ReactPHP integruje się z react/async, które korzysta z Fibers dostępnych w PHP 8.1, dzięki czemu można użyć await() dla promisów i pisać kod w sposób liniowy. Opakowanie coroutine()/async() uruchamia Państwa funkcję wewnątrz Fiberu.

<?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();

Nieblokujący serwer TCP

ReactPHP doskonale sprawdza się w przypadku serwerów. SocketServer akceptuje połączenia i emituje zdarzenia; każde połączenie jest samo w sobie nieblokującym strumieniem. Jeden proces obsługuje tysiące jednoczesnych klientów.

<?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";

Serwer HTTP w kilku wierszach

Serwer react/http obsługuje żądania za pomocą callbacku zwracającego obiekt Response (lub promis z takim obiektem). Ponieważ jest to proces działający stale, zależności inicjalizuje się raz — ale za zarządzanie pamięcią między żądaniami odpowiada już aplikacja.

<?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";

Strumieniowanie bez buforowania

Strumienie ReactPHP implementują mechanizm back-pressure: można użyć pipe(), aby połączyć strumień odczytywalny z zapisywalnym, a środowisko wykonawcze automatycznie reguluje tempo. Dzięki temu można przesyłać gigabajty przy stałym, niewielkim zużyciu pamięci, zamiast ładować pliki do pamięci 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

Grzech główny: blokowanie pętli

Wszystko przestaje działać w chwili wywołania funkcji blokującej wewnątrz handlera — sleep(), blokującego PDO, curl_exec czy file_get_contents dla adresu URL. Funkcje te zamrażają pojedynczy wątek i zatrzymują każde połączenie. Należy używać odpowiedników ReactPHP: Loop::addTimer do opóźnień, clue/reactphp-* lub asynchronicznych sterowników baz danych do zapytań oraz Browser do obsługi 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

Ograniczanie współbieżności

Nieograniczone rozdzielanie pracy może wyczerpać deskryptory plików albo przeciążyć zewnętrzne API. clue/mq-react firmy ReactPHP kolejkuje zadania z limitem współbieżności, przetwarzając jednocześnie N promisów, podczas gdy reszta czeka. W ten sposób ogranicza obciążenie generowane przez własny ruch wychodzący.

<?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";
});

Szybki test

Jak odczekać 2 sekundy w handlerze ReactPHP bez zakłócania współbieżności?

Podsumowanie

ReactPHP wnosi nieblokujące operacje wejścia-wyjścia do czystego PHP:

  • Pojedyncza pętla zdarzeń (fasada Loop) obsługuje timery, gniazda i strumienie — nie jest wymagane żadne rozszerzenie.
  • Operacje zwracają promisy; all() wykonuje żądania równolegle, a react/async i Fibers pozwalają używać await() w kodzie wyglądającym jak synchroniczny.
  • Można budować trwałe serwery TCP/HTTP i strumienie z kontrolą przepływu wstecznego, które obsługują tysiące połączeń w jednym procesie.
  • Nie wolno blokować pętli — należy zastępować sleep i blokujące operacje wejścia-wyjścia timerami ReactPHP oraz asynchronicznymi sterownikami.

Często zadawane pytania

Czy lekcja „Pętle zdarzeń z ReactPHP” jest bezpłatna?

Tak — pełny tekst „Pętle zdarzeń z ReactPHP” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Pętle zdarzeń z ReactPHP”?

Proszę uruchamiać nieblokujące operacje wejścia-wyjścia za pomocą pętli zdarzeń ReactPHP. Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć PHP Academy?

Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Pętle zdarzeń z ReactPHP”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?

Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Model współbieżności PHP
  2. Fibers w PHP 8.1
  3. Pętle zdarzeń z ReactPHP
  4. Wydajne serwery z Swoole
← Powrót do PHP Academy