Milko Kosturkov
An experienced developer specializing in the web.
final class Fiber
{
public function __construct(callable $callback) {}
public function start(mixed ...$args): mixed {}
public function resume(mixed $value = null): mixed {}
public function throw(Throwable $exception): mixed {}
public function isStarted(): bool {}
public function isSuspended(): bool {}
public function isRunning(): bool {}
public function isTerminated(): bool {}
public function getReturn(): mixed {}
public static function getCurrent(): ?self {}
public static function suspend(mixed $value = null): mixed {}
}
final class ReflectionFiber
{
public function __construct(Fiber $fiber) {}
public function getFiber(): Fiber {}
public function getExecutingFile(): string {}
public function getExecutingLine(): int {}
public function getTrace(int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT): array {}
public function isStarted(): bool {}
public function isSuspended(): bool {}
public function isRunning(): bool {}
public function isTerminated(): bool {}
}
<?php
$echo = function (string $name) {
echo "This is fiber $name awaiting to be resumed\n";
$input = Fiber::suspend($name);
echo "$name is from the $input guys\n";
return $name;
};
$f1 = new Fiber($echo);
$f2 = new Fiber($echo);
$r11 = $f1->start("Optimus");
$r21 = $f2->start("Megatron");
echo "F1 was suspended with $r11\n";
echo "F2 was suspended with $r21\n";
$f2->resume("bad");
$f1->resume("good");
echo "F1 returned {$f1->getReturn()}\n";
echo "F2 returned {$f2->getReturn()}\n";
This is fiber Optimus awaiting to be resumed
This is fiber Megatron awaiting to be resumed
F1 was suspended with Optimus
F2 was suspended with Megatron
Megatron is from the bad guys
Optimus is from the good guys
F1 returned Optimus
F2 returned Megatron
<?php
$top = new Fiber(function () {
echo "Starting top fiber\n";
$nested = new Fiber(function () {
echo "Starting nested\n";
Fiber::suspend();
echo "Ending nested\n";
});
$nested->start();
echo "Ending top\n";
return $nested;
});
$top->start();
$nested = $top->getReturn();
$nested->resume();
Starting top fiber
Starting nested
Ending top
Ending nested
<?php
$file1 = file_get_contents('http://example.com/file1.txt');
$file2 = file_get_contents('http://example.com/file2.txt');
<?php
function fetchUrl(string $url) {
$fp = @stream_socket_client("tcp://$url:80", $errno, $errstr, 30);
if (!$fp) {
throw new Exception($errstr);
}
stream_set_blocking($fp, false);
fwrite($fp, "GET / HTTP/1.0\r\nHost: $url\r\nAccept: */*\r\n\r\n");
$content = '';
while (!feof($fp)) {
$bytes = fgets($fp, 100);
$content .= $bytes;
}
return $content;
}
<?php
class URLFetcher
{
public string $content = '';
private $fp;
public function __construct(private string $url)
{
}
public function start(): void
{
$this->fp = @stream_socket_client("tcp://$this->url:80", $errno, $errstr, 30);
if (!$this->fp) {
throw new Exception($errstr);
}
stream_set_blocking($this->fp, false);
fwrite($this->fp, "GET / HTTP/1.0\r\nHost: $this->url\r\nAccept: */*\r\n\r\n");
}
public function readSomeBytes(): void
{
$this->content .= fgets($this->fp, 100);
}
public function isDone(): bool
{
return feof($this->fp);
}
}
<?php
function fetchUrl(string $url) {
$fetcher = new URLFetcher($url);
$fetcher->start();
while (!$fetcher->isDone()) {
$fetcher->readSomeBytes();
}
return $fetcher->content;
}
<?php
class Loop
{
private static array $callbacks = [];
public static function add(callable $callback)
{
self::$callbacks[] = $callback;
}
public static function run()
{
while (count(self::$callbacks)) {
$cb = array_shift(self::$callbacks);
$cb();
}
}
}
<?php
class URLFetcher
{
public string $content = '';
private $fp;
public function __construct(
private string $url,
private Closure $done,
private Closure $onerror
) {}
public function start(): void
{
$this->fp = @stream_socket_client("tcp://$this->url:80", $errno, $errstr, 30);
if (!$this->fp) {
($this->onerror)($errstr);
}
stream_set_blocking($this->fp, false);
fwrite($this->fp, "GET / HTTP/1.0\r\nHost: $this->url\r\nAccept: */*\r\n\r\n");
Loop::add(fn () => $this->tick());
}
public function tick(): void
{
if ($this->isDone()) {
fclose($this->fp);
($this->done)($this->content);
} else {
$this->readSomeBytes();
Loop::add(fn () => $this->tick());
}
}
public function readSomeBytes(): void
{
$this->content .= fgets($this->fp, 100);
}
public function isDone(): bool
{
return feof($this->fp);
}
}
<?php
function fetchUrl(string $url, callable $done, callable $onerror) {
$fetcher = new URLFetcher(
$url,
Closure::fromCallable($done),
Closure::fromCallable($onerror)
);
Loop::add(fn () => $fetcher->start());
}
<?php
Loop::add(function () {
echo "Fetching dir.bg\n";
fetchUrl(
'www.dir.bg',
function (string $dirbg) {
echo "Got dir.bg\n";
echo $dirbg;
},
function (string $err) {
echo "Got error from dir.bg\n";
echo "err\n";
}
);
echo "Fetching google.com\n";
fetchUrl(
'www.google.com',
function (string $google) {
echo "Got Google\n";
echo "Size is: " . strlen($google) . "\n";
},
function (string $err) {
echo "Got error from google\n";
echo "err\n";
}
);
});
Loop::run();
Fetching dir.bg
Fetching google.com
Got dir.bg
HTTP/1.1 301 Moved Permanently
Content-length: 0
Location: https://dir.bg/
Connection: close
Got Google
Size is: 51803
function ffetchUrl(string $url): string {
$fiber = Fiber::getCurrent();
fetchUrl(
$url,
fn ($value) => $fiber->resume($value),
fn ($err) => $fiber->throw(new Exception($err))
);
return Fiber::suspend();
}
function defer(callable $coroutine) {
$fiber = new Fiber($coroutine);
Loop::add(fn () => $fiber->start());
}
defer(function () {
echo "Fetching dir.bg\n";
try {
$dirbg = ffetchUrl('www.dir.bg');
echo "Got dir.bg\n";
echo $dirbg;
} catch (Exception $e) {
echo "Got error from dir.bg\n";
echo $e->getMessage() . "\n";
}
});
defer(function () {
echo "Fetching google.com\n";
try {
$google = ffetchUrl('www.google.com');
echo "Got Google\n";
echo "Size is: " . strlen($google) . "\n";
} catch (Exception $e) {
echo "Got error from google\n";
echo $e->getMessage() . "\n";
}
});
Loop::run();
Fetching dir.bg
Fetching google.com
Got dir.bg
HTTP/1.1 301 Moved Permanently
Content-length: 0
Location: https://dir.bg/
Connection: close
Got Google
Size is: 51749
They both can:
Fibers:
Fibers and generators can be combined to produce async generators
Milko Kosturkov
@mkosturkov
linkedin.com/in/milko-kosturkov
mailto: mkosturkov@gmail.com
These slides:
https://slides.com/milkokosturkov/php-fibers
By Milko Kosturkov
An overview of PHP Fibers