Fibers weszły do PHP w wersji 8.1 jako mechanizm cooperative multitasking – możliwość zawieszenia i wznowienia wykonania funkcji w dowolnym miejscu. To nie są wątki ani procesy – Fiber działa w tym samym wątku PHP, ale pozwala przełączać się między zadaniami bez blokowania. Pokażę jak działają Fibers, gdzie mają sens i jak biblioteki async (ReactPHP, Amp) używają ich pod spodem.
Czym jest Fiber
Fiber to funkcja która może być zawieszona (Fiber::suspend()) i wznowiona ($fiber->resume()). Różnica od zwykłej funkcji: nie musi dobiec do końca zanim sterowanie wróci do wywołującego. Różnica od generatora: Fiber może przyjmować wartości przy wznowieniu i zwracać wartości przy zawieszeniu w obie strony.
<?php
declare(strict_types=1);
// Podstawowy Fiber
$fiber = new Fiber(function(): string {
echo "Fiber: start\n";
$value = Fiber::suspend('pierwsza pauza'); // zawieś, zwróć wartość do wywołującego
echo "Fiber: wznowiony z wartością: {$value}\n";
$value2 = Fiber::suspend('druga pauza');
echo "Fiber: wznowiony ponownie: {$value2}\n";
return 'fiber zakończony';
});
$result1 = $fiber->start(); // uruchom do pierwszego suspend()
echo "Główny kod otrzymał: {$result1}\n"; // "pierwsza pauza"
$result2 = $fiber->resume('dane 1'); // wznów, przekaż wartość
echo "Główny kod otrzymał: {$result2}\n"; // "druga pauza"
$fiber->resume('dane 2'); // wznów do końca
echo "Fiber zakończony: " . $fiber->getReturn() . "\n";
// Output:
// Fiber: start
// Główny kod otrzymał: pierwsza pauza
// Fiber: wznowiony z wartością: dane 1
// Główny kod otrzymał: druga pauza
// Fiber: wznowiony ponownie: dane 2
// Fiber zakończony: fiber zakończony
Scheduler – przełączanie między Fiberami
<?php
declare(strict_types=1);
// Prosty scheduler który przełącza się między Fiberami
class FiberScheduler
{
/** @var Fiber[] */
private array $fibers = [];
private array $queue = [];
public function add(Fiber $fiber): void
{
$this->fibers[] = $fiber;
$this->queue[] = $fiber;
}
public function run(): void
{
// Uruchom wszystkie Fibery
foreach ($this->fibers as $fiber) {
$fiber->start();
}
// Przetwarzaj kolejkę dopóki są zawieszone Fibery
while (!empty($this->queue)) {
$fiber = array_shift($this->queue);
if ($fiber->isSuspended()) {
$fiber->resume();
if ($fiber->isSuspended()) {
$this->queue[] = $fiber; // z powrotem do kolejki
}
}
}
}
}
// Symulacja "asynchronicznych" zadań
function fetchData(string $url, int $delay): string
{
echo "Pobieranie {$url}...\n";
Fiber::suspend(); // oddaj sterowanie
// Symulacja czekania (w prawdziwym kodzie: nieblokujący I/O)
for ($i = 0; $i < $delay; $i++) {
Fiber::suspend();
}
return "dane z {$url}";
}
$scheduler = new FiberScheduler();
$scheduler->add(new Fiber(function() {
$result = fetchData('https://api.example.com/orders', 2);
echo "Gotowe: {$result}\n";
}));
$scheduler->add(new Fiber(function() {
$result = fetchData('https://api.example.com/products', 1);
echo "Gotowe: {$result}\n";
}));
$scheduler->run();
// products kończy się przed orders (mniejszy delay)
// bez Fiberów byłyby sekwencyjne
Fibers w ReactPHP
<?php
// ReactPHP od wersji 3.0 używa Fiberów pod spodem
// pozwala pisać kod synchronicznie który działa asynchronicznie
require 'vendor/autoload.php';
use React\Http\Browser;
use React\EventLoop\Loop;
// Stary styl (Promise-based)
$browser = new Browser();
$browser->get('https://httpbin.org/get')
->then(function(\Psr\Http\Message\ResponseInterface $response) {
echo $response->getBody();
});
// Nowy styl (Fiber-based, ReactPHP 3+)
// Wygląda synchronicznie, działa asynchronicznie
React\Async\async(function() {
$browser = new Browser();
$response1 = React\Async\await($browser->get('https://httpbin.org/get'));
$response2 = React\Async\await($browser->get('https://httpbin.org/post'));
// Oba requesty wykonują się sekwencyjnie w kodzie
// ale event loop może je przeplatać
echo strlen((string)$response1->getBody()) . "\n";
echo strlen((string)$response2->getBody()) . "\n";
})();
Fibers vs Generatory vs Promises
| Mechanizm | Zawieszenie | Komunikacja | Zastosowanie |
|---|---|---|---|
| Generator | yield | Jednostronna (yield value) | Iteracja, lazy sequences |
| Fiber | Fiber::suspend() | Dwustronna (suspend/resume) | Cooperative multitasking |
| Promise | then/catch | Callback-based | Async API calls |
| async/await | await (nad Fiberem) | Synchroniczny styl | Czytelny async kod |
Praktyczne zastosowanie – batch processor
<?php
declare(strict_types=1);
// Przetwarzanie dużej kolekcji bez blokowania innych operacji
class BatchProcessor
{
private array $pendingFibers = [];
public function process(array $items, callable $handler, int $concurrency = 5): array
{
$results = [];
$chunks = array_chunk($items, $concurrency);
foreach ($chunks as $chunk) {
$fibers = [];
foreach ($chunk as $key => $item) {
$fiber = new Fiber(function() use ($item, $handler, $key, &$results) {
$results[$key] = $handler($item);
});
$fibers[] = $fiber;
$fiber->start();
}
// Dokończ wszystkie Fibery w tym chunku
$running = true;
while ($running) {
$running = false;
foreach ($fibers as $fiber) {
if ($fiber->isSuspended()) {
$fiber->resume();
$running = true;
}
}
}
}
return $results;
}
}
// Użycie
$processor = new BatchProcessor();
$items = range(1, 100);
$results = $processor->process($items, function(int $item): int {
Fiber::suspend(); // symulacja I/O
return $item * 2;
}, concurrency: 10);
echo count($results) . " wyników przetworzonych\n";
Podsumowanie
Fibers to niskopoziomowy mechanizm cooperative multitasking w PHP. Nie zastępują wątków ani procesów – działają w jednym wątku. Ich prawdziwa siła ujawnia się przez biblioteki wyższego poziomu: ReactPHP, Amp, Revolt. Bezpośrednie użycie Fiberów jest rzadkie – częściej piszesz kod używając async/await z tych bibliotek, które pod spodem zarządzają Fiberami. W kontekście Magento 2 Fibers mogą być użyteczne w skryptach CLI do równoległego przetwarzania dużych kolekcji danych.
