', $sitesContent); } $return = downloadAllSites($sites); ``` Jak widać ściągamy treść każdej ze stron po kolei. Na koniec łączymy wszystko w jedno. o ile chcielibyśmy żeby ściąganie plików działało asynchronicznie, to wystarczy metodę `get` zamienić na `getAsync`. Metoda ta zwraca nam obiekt `Promise`. Obiekt ten reprezentuje wynik, który zostanie zwrócony po wywołaniu asynchronicznej funkcji. ``` function downloadSite($siteUrl) { $client = new GuzzleHttp\Client(); return $client->getAsync($siteUrl); } function downloadAllSites($sites) { $results = []; foreach ($sites as $siteUrl) { $results[] = downloadSite($siteUrl); } //return ?? //i co tu możemy zwrócić? } ``` Niestety mając zwrócone obiekty typu *promise*, nie możemy ich połączyć do momentu, w którym wszystkie te funkcje się nie wykonają tak, jak nie możemy zwrócić obiekt typu *promise*. Problem ten świetnie opisuje artykuł [*Jakiego koloru jest Twoja funkcja?*](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/). W bardzo dużym uproszczeniu ww. artykuł opisuje zalety i wady programowania asynchronicznego. Przestrzega przed asynchronicznością, wskazując przede wszystkim na to, iż wywołanie funkcji asynchronicznej zmusza nas do traktowania całego stacka funkcji jako asynchronicznego. Odpowiedzią na ten problem, w wersji PHP 8.1 jest **Fibers** - rozwiązanie, z powodzeniem stosowane już w języku Ruby, które pozwala nam zastosować asynchroniczność bez przepisywania całego kodu. Oczywiście, jak widać na powyższym przykładzie, moglibyśmy odczekać, aż nasze funkcje asynchroniczne się wykonają, wywołując na nich metodę `wait`, ale to rozwiązanie pomijam, bo przez nie tracimy korzyści, wynikające z asynchroniczności. ## Fibers Fibers reprezentują przerywalne funkcje. Mogą być przerwane w dowolnym momencie i pozostać zawieszone do czasu ich wznowienia. Najlepiej przedstawia to przykład z dokumentacji PHP: ``` $fiber = new Fiber(function (): void { $value = Fiber::suspend('fiber'); echo "Value used to resume fiber: ", $value, PHP_EOL; }); $value = $fiber->start(); echo "Value from fiber suspending: ", $value, PHP_EOL; $fiber->resume('test'); ``` Przykład ten wyświetli nam: *Value from fiber suspending: fiber* *Value used to resume fiber: test* Przyjrzyjmy się, co w tym przykładzie dzieje się po kolei. Do konstruktora klasy `Fibers` przekazujemy callback, która będzie wywołana w momencie uruchomienia metody `start()`. najważniejsze w tej funkcji jest wywołanie `Fiber::suspend()`. Przerywa to działanie funkcji przekazanej w konstruktorze. Funkcja jest wznawiana dopiero w momencie wywołania metody `resume `na obiekcie `“fiber”`. Innymi ciekawymi metodami klasy `Fibers` są: * `isTerminated`, informująca nas, czy callback przekazany do konstruktora się już wykonała, * `getReturn`, zwracająca to samo, co callback po wykonaniu. Gdy pierwotnie zobaczyłem ten przykład, nie do końca rozumiałem, jakie może być jego zastosowanie. Odpowiedź odnalazłem, dopiero czytając RFC. Fibers są stworzone po to, by można było wywoływać funkcje asynchroniczne, bez przepisywania całego stosu wywołań funkcji. A zatem jest to odpowiedź na problem opisany na początku tego wpisu oraz w artykule [*Jakiego koloru jest Twoja funkcja?*](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/). Spróbujmy napisać asynchroniczny kod, który równie dobrze mógłby się znaleźć wewnątrz funkcji synchronicznej: ``` use Spatie\Async\Pool; $fiber = new Fiber(function (): string { $processes = [ 'operacja 1', 'operacja 2', 'operacja 3', ]; $pool = Pool::create(); $result = new stdClass(); $result->result = ''; foreach ($processes as $processName) { $pool->add(function() use($processName){ $operationTime = rand(1, 15); sleep($operationTime); return $processName . ' zajeła ' . $operationTime . ' sekund' . PHP_EOL; }) ->then(function($output) use ($result) { $result->result .= $output; }); } while (count($pool->getFinished()) !== count($processes)) { Fiber::suspend(); $pool->notify(); } return $result->result; }); $value = $fiber->start(); while ($fiber->isTerminated() === false) { sleep(1); echo 'W tym miejscu w kodzie, możesz dokonać dowolną operację'. PHP_EOL; $fiber->resume(); } echo $fiber->getReturn(); ``` Nasz kod co jakiś czas sprawdza, czy asynchroniczne wywołania już się wykonały. A w międzyczasie pozwala nam na wykonywanie własnego kodu. Ten przykład, na standardowym wyjściu, wyświetli nam mniej więcej taki rezultat: ``` W tym miejscu w kodzie, możesz dokonać dowolną operację W tym miejscu w kodzie, możesz dokonać dowolną operację W tym miejscu w kodzie, możesz dokonać dowolną operację W tym miejscu w kodzie, możesz dokonać dowolną operację W tym miejscu w kodzie, możesz dokonać dowolną operację W tym miejscu w kodzie, możesz dokonać dowolną operację W tym miejscu w kodzie, możesz dokonać dowolną operację W tym miejscu w kodzie, możesz dokonać dowolną operację operacja 1 zajeła 4 sekund operacja 3 zajeła 5 sekund operacja 2 zajeła 7 sekund ``` ## Podsumowanie Fibers są mało znanym i mało opisywanym mechanizmem PHP. o ile wejdziemy głębiej w ten temat, okazuje się, iż dają nam rozwiązanie na dość typowy problem przy programowaniu asynchronicznym. Dzięki nim możemy dodać w jednym punkcie naszej aplikacji asynchroniczność, bez konieczności przepisywania całej aplikacji.
Fibers w PHP - jak ułatwić wdrożenie asynchroniczności w projekcie
', $sitesContent); } $return = downloadAllSites($sites); ``` Jak widać ściągamy treść każdej ze stron po kolei. Na koniec łączymy wszystko w jedno. o ile chcielibyśmy żeby ściąganie plików działało asynchronicznie, to wystarczy metodę `get` zamienić na `getAsync`. Metoda ta zwraca nam obiekt `Promise`. Obiekt ten reprezentuje wynik, który zostanie zwrócony po wywołaniu asynchronicznej funkcji. ``` function downloadSite($siteUrl) { $client = new GuzzleHttp\Client(); return $client->getAsync($siteUrl); } function downloadAllSites($sites) { $results = []; foreach ($sites as $siteUrl) { $results[] = downloadSite($siteUrl); } //return ?? //i co tu możemy zwrócić? } ``` Niestety mając zwrócone obiekty typu *promise*, nie możemy ich połączyć do momentu, w którym wszystkie te funkcje się nie wykonają tak, jak nie możemy zwrócić obiekt typu *promise*. Problem ten świetnie opisuje artykuł [*Jakiego koloru jest Twoja funkcja?*](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/). W bardzo dużym uproszczeniu ww. artykuł opisuje zalety i wady programowania asynchronicznego. Przestrzega przed asynchronicznością, wskazując przede wszystkim na to, iż wywołanie funkcji asynchronicznej zmusza nas do traktowania całego stacka funkcji jako asynchronicznego. Odpowiedzią na ten problem, w wersji PHP 8.1 jest **Fibers** - rozwiązanie, z powodzeniem stosowane już w języku Ruby, które pozwala nam zastosować asynchroniczność bez przepisywania całego kodu. Oczywiście, jak widać na powyższym przykładzie, moglibyśmy odczekać, aż nasze funkcje asynchroniczne się wykonają, wywołując na nich metodę `wait`, ale to rozwiązanie pomijam, bo przez nie tracimy korzyści, wynikające z asynchroniczności. ## Fibers Fibers reprezentują przerywalne funkcje. Mogą być przerwane w dowolnym momencie i pozostać zawieszone do czasu ich wznowienia. Najlepiej przedstawia to przykład z dokumentacji PHP: ``` $fiber = new Fiber(function (): void { $value = Fiber::suspend('fiber'); echo "Value used to resume fiber: ", $value, PHP_EOL; }); $value = $fiber->start(); echo "Value from fiber suspending: ", $value, PHP_EOL; $fiber->resume('test'); ``` Przykład ten wyświetli nam: *Value from fiber suspending: fiber* *Value used to resume fiber: test* Przyjrzyjmy się, co w tym przykładzie dzieje się po kolei. Do konstruktora klasy `Fibers` przekazujemy callback, która będzie wywołana w momencie uruchomienia metody `start()`. najważniejsze w tej funkcji jest wywołanie `Fiber::suspend()`. Przerywa to działanie funkcji przekazanej w konstruktorze. Funkcja jest wznawiana dopiero w momencie wywołania metody `resume `na obiekcie `“fiber”`. Innymi ciekawymi metodami klasy `Fibers` są: * `isTerminated`, informująca nas, czy callback przekazany do konstruktora się już wykonała, * `getReturn`, zwracająca to samo, co callback po wykonaniu. Gdy pierwotnie zobaczyłem ten przykład, nie do końca rozumiałem, jakie może być jego zastosowanie. Odpowiedź odnalazłem, dopiero czytając RFC. Fibers są stworzone po to, by można było wywoływać funkcje asynchroniczne, bez przepisywania całego stosu wywołań funkcji. A zatem jest to odpowiedź na problem opisany na początku tego wpisu oraz w artykule [*Jakiego koloru jest Twoja funkcja?*](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/). Spróbujmy napisać asynchroniczny kod, który równie dobrze mógłby się znaleźć wewnątrz funkcji synchronicznej: ``` use Spatie\Async\Pool; $fiber = new Fiber(function (): string { $processes = [ 'operacja 1', 'operacja 2', 'operacja 3', ]; $pool = Pool::create(); $result = new stdClass(); $result->result = ''; foreach ($processes as $processName) { $pool->add(function() use($processName){ $operationTime = rand(1, 15); sleep($operationTime); return $processName . ' zajeła ' . $operationTime . ' sekund' . PHP_EOL; }) ->then(function($output) use ($result) { $result->result .= $output; }); } while (count($pool->getFinished()) !== count($processes)) { Fiber::suspend(); $pool->notify(); } return $result->result; }); $value = $fiber->start(); while ($fiber->isTerminated() === false) { sleep(1); echo 'W tym miejscu w kodzie, możesz dokonać dowolną operację'. PHP_EOL; $fiber->resume(); } echo $fiber->getReturn(); ``` Nasz kod co jakiś czas sprawdza, czy asynchroniczne wywołania już się wykonały. A w międzyczasie pozwala nam na wykonywanie własnego kodu. Ten przykład, na standardowym wyjściu, wyświetli nam mniej więcej taki rezultat: ``` W tym miejscu w kodzie, możesz dokonać dowolną operację W tym miejscu w kodzie, możesz dokonać dowolną operację W tym miejscu w kodzie, możesz dokonać dowolną operację W tym miejscu w kodzie, możesz dokonać dowolną operację W tym miejscu w kodzie, możesz dokonać dowolną operację W tym miejscu w kodzie, możesz dokonać dowolną operację W tym miejscu w kodzie, możesz dokonać dowolną operację W tym miejscu w kodzie, możesz dokonać dowolną operację operacja 1 zajeła 4 sekund operacja 3 zajeła 5 sekund operacja 2 zajeła 7 sekund ``` ## Podsumowanie Fibers są mało znanym i mało opisywanym mechanizmem PHP. o ile wejdziemy głębiej w ten temat, okazuje się, iż dają nam rozwiązanie na dość typowy problem przy programowaniu asynchronicznym. Dzięki nim możemy dodać w jednym punkcie naszej aplikacji asynchroniczność, bez konieczności przepisywania całej aplikacji.