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 fileGrzech 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/coroutineOgraniczanie 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, areact/asynci 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ć
sleepi 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
- Model współbieżności PHP
- Fibers w PHP 8.1
- Pętle zdarzeń z ReactPHP
- Wydajne serwery z Swoole