friendship ended with social-app. php is my new best friend

aaaaaaaaaaaaa

+3 -1
composer.json
···
"chillerlan/php-oauth": "^1.0",
"tracy/tracy": "^2.10",
"flightphp/runway": "^1.1",
-
"fusonic/opengraph": "^3.0"
+
"fusonic/opengraph": "^3.0",
+
"react/promise": "^3.2",
+
"react/async": "^4.3"
},
"require-dev": {
"flightphp/tracy-extensions": "^0.2.7"
+221 -1
composer.lock
···
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
-
"content-hash": "084207acd9d27dbf5822b930886b2c3d",
+
"content-hash": "d1b9a49dbc0044c8b668ec563b93ed20",
"packages": [
{
"name": "adhocore/cli",
···
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
},
"time": "2019-03-08T08:55:37+00:00"
+
},
+
{
+
"name": "react/async",
+
"version": "v4.3.0",
+
"source": {
+
"type": "git",
+
"url": "https://github.com/reactphp/async.git",
+
"reference": "635d50e30844a484495713e8cb8d9e079c0008a5"
+
},
+
"dist": {
+
"type": "zip",
+
"url": "https://api.github.com/repos/reactphp/async/zipball/635d50e30844a484495713e8cb8d9e079c0008a5",
+
"reference": "635d50e30844a484495713e8cb8d9e079c0008a5",
+
"shasum": ""
+
},
+
"require": {
+
"php": ">=8.1",
+
"react/event-loop": "^1.2",
+
"react/promise": "^3.2 || ^2.8 || ^1.2.1"
+
},
+
"require-dev": {
+
"phpstan/phpstan": "1.10.39",
+
"phpunit/phpunit": "^9.6"
+
},
+
"type": "library",
+
"autoload": {
+
"files": [
+
"src/functions_include.php"
+
],
+
"psr-4": {
+
"React\\Async\\": "src/"
+
}
+
},
+
"notification-url": "https://packagist.org/downloads/",
+
"license": [
+
"MIT"
+
],
+
"authors": [
+
{
+
"name": "Christian Lück",
+
"email": "christian@clue.engineering",
+
"homepage": "https://clue.engineering/"
+
},
+
{
+
"name": "Cees-Jan Kiewiet",
+
"email": "reactphp@ceesjankiewiet.nl",
+
"homepage": "https://wyrihaximus.net/"
+
},
+
{
+
"name": "Jan Sorgalla",
+
"email": "jsorgalla@gmail.com",
+
"homepage": "https://sorgalla.com/"
+
},
+
{
+
"name": "Chris Boden",
+
"email": "cboden@gmail.com",
+
"homepage": "https://cboden.dev/"
+
}
+
],
+
"description": "Async utilities and fibers for ReactPHP",
+
"keywords": [
+
"async",
+
"reactphp"
+
],
+
"support": {
+
"issues": "https://github.com/reactphp/async/issues",
+
"source": "https://github.com/reactphp/async/tree/v4.3.0"
+
},
+
"funding": [
+
{
+
"url": "https://opencollective.com/reactphp",
+
"type": "open_collective"
+
}
+
],
+
"time": "2024-06-04T14:40:02+00:00"
+
},
+
{
+
"name": "react/event-loop",
+
"version": "v1.5.0",
+
"source": {
+
"type": "git",
+
"url": "https://github.com/reactphp/event-loop.git",
+
"reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354"
+
},
+
"dist": {
+
"type": "zip",
+
"url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354",
+
"reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354",
+
"shasum": ""
+
},
+
"require": {
+
"php": ">=5.3.0"
+
},
+
"require-dev": {
+
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+
},
+
"suggest": {
+
"ext-pcntl": "For signal handling support when using the StreamSelectLoop"
+
},
+
"type": "library",
+
"autoload": {
+
"psr-4": {
+
"React\\EventLoop\\": "src/"
+
}
+
},
+
"notification-url": "https://packagist.org/downloads/",
+
"license": [
+
"MIT"
+
],
+
"authors": [
+
{
+
"name": "Christian Lück",
+
"email": "christian@clue.engineering",
+
"homepage": "https://clue.engineering/"
+
},
+
{
+
"name": "Cees-Jan Kiewiet",
+
"email": "reactphp@ceesjankiewiet.nl",
+
"homepage": "https://wyrihaximus.net/"
+
},
+
{
+
"name": "Jan Sorgalla",
+
"email": "jsorgalla@gmail.com",
+
"homepage": "https://sorgalla.com/"
+
},
+
{
+
"name": "Chris Boden",
+
"email": "cboden@gmail.com",
+
"homepage": "https://cboden.dev/"
+
}
+
],
+
"description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
+
"keywords": [
+
"asynchronous",
+
"event-loop"
+
],
+
"support": {
+
"issues": "https://github.com/reactphp/event-loop/issues",
+
"source": "https://github.com/reactphp/event-loop/tree/v1.5.0"
+
},
+
"funding": [
+
{
+
"url": "https://opencollective.com/reactphp",
+
"type": "open_collective"
+
}
+
],
+
"time": "2023-11-13T13:48:05+00:00"
+
},
+
{
+
"name": "react/promise",
+
"version": "v3.3.0",
+
"source": {
+
"type": "git",
+
"url": "https://github.com/reactphp/promise.git",
+
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a"
+
},
+
"dist": {
+
"type": "zip",
+
"url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a",
+
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a",
+
"shasum": ""
+
},
+
"require": {
+
"php": ">=7.1.0"
+
},
+
"require-dev": {
+
"phpstan/phpstan": "1.12.28 || 1.4.10",
+
"phpunit/phpunit": "^9.6 || ^7.5"
+
},
+
"type": "library",
+
"autoload": {
+
"files": [
+
"src/functions_include.php"
+
],
+
"psr-4": {
+
"React\\Promise\\": "src/"
+
}
+
},
+
"notification-url": "https://packagist.org/downloads/",
+
"license": [
+
"MIT"
+
],
+
"authors": [
+
{
+
"name": "Jan Sorgalla",
+
"email": "jsorgalla@gmail.com",
+
"homepage": "https://sorgalla.com/"
+
},
+
{
+
"name": "Christian Lück",
+
"email": "christian@clue.engineering",
+
"homepage": "https://clue.engineering/"
+
},
+
{
+
"name": "Cees-Jan Kiewiet",
+
"email": "reactphp@ceesjankiewiet.nl",
+
"homepage": "https://wyrihaximus.net/"
+
},
+
{
+
"name": "Chris Boden",
+
"email": "cboden@gmail.com",
+
"homepage": "https://cboden.dev/"
+
}
+
],
+
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
+
"keywords": [
+
"promise",
+
"promises"
+
],
+
"support": {
+
"issues": "https://github.com/reactphp/promise/issues",
+
"source": "https://github.com/reactphp/promise/tree/v3.3.0"
+
},
+
"funding": [
+
{
+
"url": "https://opencollective.com/reactphp",
+
"type": "open_collective"
+
}
+
],
+
"time": "2025-08-19T18:57:03+00:00"
},
"name": "symfony/css-selector",
+1
css/_partials/_globals.scss
···
[popover] {
max-height: calc(100% - 40px);
max-width: calc(100% - 40px);
+
min-width: 280px;
display: none;
grid-template-rows: 1fr auto;
border: 1px var(--border-color) solid;
+2 -1
css/_partials/_popovers.scss
···
}
#likes,
-
#reposts {
+
#reposts,
+
#quotes {
.inner {
display: grid;
gap: 10px;
+8 -2
css/_partials/_post.scss
···
-
.post {
+
div.post {
border-bottom: 1px var(--border-color) solid;
padding: 10px;
width: 100%;
···
}
}
-
.postInteraction {
+
.actions,
+
#interactions {
display: flex;
gap: 10px;
···
color: inherit;
background-color: transparent;
cursor: pointer;
+
text-transform: lowercase;
}
+
}
+
+
#interactions {
+
border-bottom: 1px var(--border-color) solid;
}
#replies {
+5 -1
css/_partials/_profile.scss
···
aspect-ratio: 740/247;
margin-bottom: 25px;
-
img {
+
img, .nobanner {
width: 100%;
height: 100%;
object-fit: cover;
+
}
+
+
.nobanner {
+
background-color: var(--btn-primary-bg);
}
}
+6 -1
css/_partials/_profileMini.scss
···
.profileMini {
+
min-width: 250px;
+
padding: 10px;
+
a {
text-decoration: none;
color: inherit;
display: grid;
grid-template-areas: "avatar name";
grid-template-columns: 48px 1fr;
-
min-width: 250px;
gap: 10px;
}
···
img {
object-fit: cover;
+
width: 100%;
+
height: 100%;
+
border-radius: 100%;
}
}
+98 -15
index.php
···
<?php
+
error_reporting(E_ALL);
+
ini_set('display_errors', 'On');
+
require_once('vendor/autoload.php');
require_once('config.php');
require_once('lib/bskyToucher.php');
use Smallnest\Bsky\BskyToucher;
use League\CommonMark\CommonMarkConverter;
+
use React\Promise\Deferred;
+
use React\EventLoop\Loop;
+
use React\Promise\Promise;
$bskyToucher = new BskyToucher();
···
});
Flight::route('/u/@handle:[a-z0-9\.]+/@rkey:[a-z0-9]+', function (string $handle, string $rkey): void {
+
global $bskyToucher, $data;
$bskyToucher = new BskyToucher();
-
$post = $bskyToucher->getPost($handle, $rkey, Flight::get('userAuth') === null);
-
$atUri = 'at://'.$post->did.'/app.bsky.feed.post/'.$rkey;
-
$latte = new Latte\Engine;
-
$latte->render('./templates/single.latte', array_merge(Flight::get('standardParams'), [
-
'mainClass' => 'post',
-
'post' => $post,
-
'likes' => $bskyToucher->getLikeUsers($atUri),
-
'reposts' => $bskyToucher->getRepostUsers($atUri),
-
'quotes' => $bskyToucher->getQuoteRecords($atUri),
-
'replies' => $bskyToucher->getReplyRecords($atUri)
-
]));
+
$deferred = new React\Promise\Deferred();
+
$data = [];
+
$deferred->promise()
+
->then(function ($x) {
+
global $bskyToucher, $data;
+
$data = [
+
'handle' => $x['handle'],
+
'post' => $bskyToucher->getPost($x['handle'], $x['rkey'], Flight::get('userAuth') === null),
+
'rkey' => $x['rkey']
+
];
+
return [
+
'handle' => $x['handle'],
+
'post' => $bskyToucher->getPost($x['handle'], $x['rkey'], Flight::get('userAuth') === null),
+
'rkey' => $x['rkey']
+
];
+
})
+
->then(function($x) {
+
global $atUri;
+
$atUri = 'at://'.$x['post']->did.'/app.bsky.feed.post/'.$x['rkey'];
+
return React\Async\parallel([
+
function () {
+
return new Promise(function ($resolve) {
+
global $bskyToucher, $atUri;
+
$resolve($bskyToucher->getLikeUsers($atUri));
+
});
+
},
+
function () {
+
return new Promise(function ($resolve) {
+
global $bskyToucher, $atUri;
+
$resolve($bskyToucher->getRepostUsers($atUri));
+
});
+
},
+
function () {
+
return new Promise(function ($resolve) {
+
global $bskyToucher, $atUri;
+
$resolve($bskyToucher->getQuoteRecords($atUri));
+
});
+
},
+
function () {
+
return new Promise(function ($resolve) {
+
global $bskyToucher, $atUri;
+
$resolve($bskyToucher->getReplyRecords($atUri));
+
});
+
}
+
])->then(function (array $results) {
+
global $x;
+
$x['likes'] = $results[0];
+
$x['reposts'] = $results[1];
+
$x['quotes'] = $results[2];
+
$x['replies'] = $results[3];
+
return $x;
+
}, function (Exception $e) {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
});
+
})->then(function($x) {
+
global $data;
+
$latte = new Latte\Engine;
+
$latte->render('./templates/single.latte', array_merge(Flight::get('standardParams'), [
+
'mainClass' => 'post',
+
'post' => $data['post'],
+
'likes' => $x['likes'],
+
'reposts' => $x['reposts'],
+
'quotes' => $x['quotes'],
+
'replies' => $x['replies']
+
]));
+
})->catch(function (Exception $e) {
+
echo 'Exception: '.$e->getMessage().PHP_EOL;
+
});
+
$deferred->resolve([
+
'handle' => $handle,
+
'rkey' => $rkey
+
]);
});
Flight::route('/u/@handle:[a-z0-9\.]+(/@tab:[a-z]+)', function (string $handle, ?string $tab): void {
+
$deferred = new React\Promise\Deferred();
+
$deferred->promise()
+
->then(function($x) {
+
$bskyToucher = new BskyToucher();
+
$user = $bskyToucher->getUserInfo($x);
+
return $user;
+
})->then(function($x) {
+
$bskyToucher = new BskyToucher();
+
$posts = $bskyToucher->getUserPosts($x->handle);
+
print_r($posts);
+
return [
+
'mainClass' => 'profile',
+
'handle' => $x->handle,
+
'posts' => $posts,
+
'user' => $x
+
];
+
});
+
$params = $deferred->resolve($handle);
$latte = new Latte\Engine;
-
$latte->render('./templates/profile.latte', array_merge(Flight::get('standardParams'), [
-
'handle' => $handle,
-
'mainClass' => 'profile'
-
]));
+
$latte->render('./templates/profile.latte', array_merge(Flight::get('standardParams'), $params));
});
Flight::route('/f/@did:did:plc:[0-9a-z]+/@name:[a-z0-9\-\_]+', function (string $did, string $name): void {
+73 -53
lib/bskyToucher.php
···
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Fusonic\OpenGraph\Consumer;
+
use React\Promise\Deferred;
+
use React\EventLoop\Loop;
+
use React\Promise\Promise;
global $log;
-
$log = new Logger('bskyToucherLog');
-
$log->pushHandler(new StreamHandler(SITE_PATH.'/smallbird-social.log', Level::Warning));
+
//$log = new Logger('bskyToucherLog');
+
//$log->pushHandler(new StreamHandler(SITE_PATH.'/smallbird-social.log', Level::Warning));
class BskyToucher {
private $bskyApiBase = 'https://public.api.bsky.app/xrpc/';
···
return 1000 * $retries;
}
);
-
$handler->push($retryMiddleware);
+
//$handler->push($retryMiddleware);
$httpClient = new Client([
'timeout' => $timeout,
-
'handler' => $handler
+
'handler' => $handler,
+
'http_errors' => false
]);
if ($method === 'GET') {
$opts['headers'] = ["Content-Type" => "application/x-www-form-urlencoded"];
···
array_splice($split_str, $facet->end + $additions, 0, '</a>');
$additions++;
} else if ($facet->type === "mention") {
-
$profileLink = '/u/@'.$facet->handle;
+
$profileLink = '/u/'.$facet->handle;
$replaceString = '<a href="'.$profileLink.'">@'.$facet->handle.'</a>';
array_splice($split_str, $facet->start + $additions, $facet->end - $facet->start, $replaceString);
$additions += 1 - ($facet->end - $facet->start);
···
/* USER INFO */
-
function getUserInfo($identifier) {
-
$userData = $this->getUserDidAndProvider($identifier);
-
$pdsData = $this->getPdsData($userData->pds, "com.atproto.repo.getRecord", [
-
"repo" => $userData->did,
-
"collection" => "app.bsky.actor.profile",
-
"rkey" => "self"
-
]);
-
-
/*$profileData = $this->getPdsData($pds, "app.bsky.actor.getProfile", [
-
'actor' => $did
-
]);*/
-
-
if ($pdsData) {
+
function getUserInfo(string $identifier): object|bool {
+
echo 'hello world';
+
print_r($identifier);
+
$userData = $this->getSlingshotIdentityMiniDoc($identifier);
+
print_r($userData);
+
$did = $userData->did;
+
$pds = $userData->pds;
+
$userInfo = $this->getSlingshotData($did, 'app.bsky.actor.profile', 'self');
+
if ($userInfo) {
return (object) [
-
'handle' => $handle,
-
'displayName' => $pdsData->value->displayName,
+
'handle' => $userData->handle,
+
'displayName' => $userInfo->value->displayName,
'did' => $did,
-
'description' => $pdsData->value->description,
-
'avatar' => property_exists($pdsData->value, 'avatar') ? $this->getMediaUrl($pds, $did, $pdsData->value->avatar->ref->{'$link'}) : null,
-
'banner' => property_exists($pdsData->value, 'banner') ? $this->getMediaUrl($pds, $did, $pdsData->value->banner->ref->{'$link'}) : null,
-
'pinnedPost' => property_exists($pdsData->value, 'pinnedPost') ? $pdsData->value->pinnedPost->uri : null,
-
'followers' => $this->getConstellationLinkData($did, 'app.bsky.graph.follow', 'subject')
+
'pds' => $pds,
+
'description' => $userInfo->value->description,
+
'avatar' => property_exists($userInfo->value, 'avatar') ? $this->getMediaUrl($pds, $did, $userInfo->value->avatar->ref->{'$link'}) : null,
+
'banner' => property_exists($userInfo->value, 'banner') ? $this->getMediaUrl($pds, $did, $userInfo->value->banner->ref->{'$link'}) : null,
+
'pinnedPost' => property_exists($userInfo->value, 'pinnedPost') ? $userInfo->value->pinnedPost->uri : null
];
}
return false;
···
/* POSTS */
-
function getPost(string $did, string $rkey, $slingshot = false):object|bool {
+
function getPost(string $identifier, string $rkey, $slingshot = false):object|bool {
if (!$slingshot) {
return false;
}
-
$ret = $this->getSlingshotData($did, "app.bsky.feed.post", $rkey);
-
$userInfo = $this->getSlingshotData($did, "app.bsky.actor.profile", "self");
+
$ret = $this->getSlingshotData($identifier, "app.bsky.feed.post", $rkey);
+
$userInfo = $this->getSlingshotData($identifier, "app.bsky.actor.profile", "self");
+
$uriComponents = $this->splitAtUri($userInfo->uri);
+
$did = $uriComponents->did;
$plcData = $this->getPlcInfoFromRecord($did);
$author = (object) [
'displayName' => $userInfo->value->displayName,
···
return [];
}
-
function getUserPosts(string $did, $auth = false, $cursor = null, $newer = false):array {
-
$userData = $this->getSlingshotIdentityMiniDoc($did, 'app.bsky.actor.profile', 'self');
-
$postData = [];
+
function getUserPosts(string $did, $auth = false, $cursor = null, $newer = false):array|bool {
+
$userData = $this->getSlingshotIdentityMiniDoc($did);
+
$postData = $this->getPdsData($userData->pds, 'com.atproto.repo.listRecords', [
+
'repo' => $did,
+
'collection' => 'app.bsky.feed.post',
+
'cursor' => $cursor
+
]);
+
+
if (!$postData) return false;
+
$deferred = new \React\Promise\Deferred();
+
$deferred->promise()
+
->then(function($x) {
+
React\Async\parallel(array_map(function($p) {
+
return function () {
+
global $p;
+
return new Promise(function ($resolve) {
+
global $p;
+
$bskyToucher = new BskyToucher;
+
$resolve($bskyToucher->sanitizePost($p));
+
});
+
};
+
}, $x))
+
->then(function (array $results) {
+
return $results;
+
})->catch(function (Exception $e) {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
});
+
}, function (Exception $e) {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
});
-
if ($auth) {
-
$postData = $this->getPdsData($userData->pds, 'com.atproto.repo.listRecords', [
-
'repo' => $did,
-
'collection' => 'app.bsky.feed.post',
-
'limit' => 100,
-
'reverse' => true,
-
'cursor' => $cursor
-
]);
-
} else {
-
$postData = $this->getPublicApiData('app.bsky.feed.getAuthorFeed', [
-
'actor' => $did
-
]);
-
$postData = array_map(function ($post) {
-
return $this->sanitizePost($post->post);
-
}, $postData->feed);
-
}
+
$postData = $deferred->resolve($postData);
+
+
if (!$postData) return false;
return $postData;
}
···
$ret = (object) [
'displayName' => property_exists($authorInfo->value, 'displayName') && $authorInfo->value->displayName !== "" ? $authorInfo->value->displayName : $authorInfo->handle,
'handle' => $authorData->handle,
-
'profileLink' => '/u/@'.$authorData->handle,
+
'profileLink' => '/u/'.$authorData->handle,
'avatar' => property_exists($authorInfo->value, 'avatar') ? $this->getMediaUrl($authorData->pds, $did, $authorInfo->value->avatar->ref->{'$link'}) : null,
'did' => $did,
'uri' => $post->uri,
'pds' => $authorData->pds,
'postId' => $rkey,
-
'postLink' => '/u/@'.$authorData->handle.'/'.$rkey,
+
'postLink' => '/u/'.$authorData->handle.'/'.$rkey,
'content' => $this->applyFacets($post->value->text, $facets),
'replyCount' => $this->getReplies($post->uri),
'repostCount' => $this->getReposts($post->uri),
···
'displayName' => property_exists($post->author, 'displayName') && $post->author->displayName !== "" ? $post->author->displayName : $post->author->handle,
'handle' => $authorData->handle,
'did' => $authorData->did,
-
'profileLink' => '/u/@'.$authorData->handle,
+
'profileLink' => '/u/'.$authorData->handle,
'avatar' => $post->author->avatar,
'banner' => property_exists($post->author, 'banner') ? $post->author->banner : null,
'uri' => $post->uri,
'pds' => $authorData->pds,
'postId' => $rkeyMatch[2],
-
'postLink' => '/u/@'.$authorData->handle.'/'.$rkeyMatch[2],
+
'postLink' => '/u/'.$authorData->handle.'/'.$rkeyMatch[2],
'content' => $this->applyFacets($post->record->text, $facets),
'replyCount' => $post->replyCount,
'repostCount' => $post->repostCount,
···
} else if ($embeds->{'$type'} === 'app.bsky.embed.record') {
$uriComponents = $this->splitAtUri($embeds->record->uri);
$post = $this->getSlingshotData($uriComponents->did, $uriComponents->collection, $uriComponents->rkey);
+
$sanitizedPost = $this->sanitizePost($post, true);
return [
(object) [
-
'post' => $this->sanitizePost($post, true)
+
'post' => $sanitizedPost
]
];
} else if ($embeds->{'$type'} === 'app.bsky.something.external') {
···
}
function sanitizeUserList(array $users):array {
-
return array_map(function ($rec) {
+
$normalized = array_map(function ($rec) {
$hydratedRec = $rec;
$actorSlingshot = $this->getSlingshotData($rec->did, 'app.bsky.actor.profile', 'self');
+
if (!$actorSlingshot) return false;
$actorData = $this->getPlcInfoFromRecord($rec->did);
$hydratedRec->actor = (object) [
'displayName' => $actorSlingshot->value->displayName !== "" ? htmlspecialchars($actorSlingshot->value->displayName) : $actorData->handle,
···
];
return $hydratedRec;
}, $users);
+
$ret = array_values(array_filter($normalized));
+
return $ret;
}
function sanitizeFacets(array $facets):array {
+10 -5
templates/_partials/embed_record.latte
···
<div class="embeds">
-
<div class="record">
-
<a href="{$embed->uri}">
-
idk
-
</a>
+
<div class="record post">
+
<div class="postHeader">
+
<div class="avatar">
+
<a href="{$embed->profileLink}"><img src="{$embed->avatar}" alt="{$embed->displayName}'s user icon" /></a>
+
</div>
+
<div class="displayName"><a href="{$embed->profileLink}">{$embed->displayName}</a></div>
+
<div class="handle"><a href="{$embed->profileLink}">{$embed->handle}</a></div>
+
<div class="timeAgo"><a href="{$embed->postLink}">{$embed->createdAt}</a></div>
+
</div>
</div>
-
</div>
+
</div>
+4 -4
templates/_partials/post.latte
···
-
<div class="post" data-uri="{$post->uri}">
+
<div class="post">
<div class="postHeader">
<div class="avatar">
<a href="{$post->profileLink}"><img src="{$post->avatar}" alt="{$post->displayName}'s user icon" /></a>
</div>
-
<div class="displayName"><a href="{$post->profileLink}">{$post->displayName}</a></div>
+
<div class="displayName"><a href="{$post->profileLink}">{$post->displayName|noescape}</a></div>
<div class="handle"><a href="{$post->profileLink}">{$post->handle}</a></div>
<div class="timeAgo"><a href="{$post->postLink}">{$post->createdAt}</a></div>
</div>
···
{include 'embed_image.latte', embeds: $post->embeds, postId: $post->postId}
{elseif $post->embedType === 'app.bsky.embed.video'}
{include 'embed_video.latte', embed: $post->embeds[0]}
-
{elseif $post->embedType === 'app.bsky.embed.link'}
+
{elseif $post->embedType === 'app.bsky.embed.external'}
{include 'embed_link.latte', embed: $post->embeds[0]}
{elseif $post->embedType === 'app.bsky.embed.record'}
{include 'embed_record.latte', embed: $post->embeds[0]}
{/if}
<div class="postFooter">
-
<div class="postInteraction">
+
<div class="actions">
<button type="button" data-action="like">like ({$post->likeCount})</button>
<button type="button" data-action="like">reply ({$post->replyCount})</button>
<button type="button" data-action="quote">quote ({$post->quoteCount})</button>
+5 -5
templates/_partials/profile_mini.latte
···
<div class="profileMini">
-
<a href="{$user->profileLink}">
+
<a href="{$profileLink}">
<div class="avatar">
-
<img src="$user->avatar" alt="" />
+
<img src="{$avatar}" alt="" />
</div>
<div class="name">
-
<span class="displayName">{$user->displayName}</span>
-
<span class="handle">{$user->handle}</span>
-
<span class="did">{$user->did}</span>
+
<span class="displayName">{$displayName}</span>
+
<span class="handle">{$handle}</span>
+
<span class="did">{$did}</span>
</div>
</a>
</div>
+31 -26
templates/profile.latte
···
-
<div class="profileCard">
-
<div class="banner">
-
<img src="{$banner}" alt="" />
-
</div>
-
<div class="avatar">
-
<img src="{$avatar}" alt="" />
-
</div>
-
<div class="profileInfo">
-
<div class="displayName">{$displayName}</div>
-
<div class="handle">{$handle}</div>
-
<div class="did">{$did}</div>
-
<div class="description">
-
{$description}
+
{layout 'layout.latte'}
+
+
{block title} | {$user->displayName} (@{$user->handle}){/block}
+
+
{block content}
+
<!--
+
{print_r($user)|noescape}
+
-->
+
<div class="profileCard">
+
<div class="banner">
+
<img src="{$user->banner}" alt="" n:if="$user->banner" />
+
<div class="nobanner" n:else></div>
</div>
-
<div class="userStats">
-
[following/followers/posts: to be implemented]
+
<div class="avatar">
+
<img src="{$user->avatar}" alt="" />
</div>
-
<div class="actions">
-
<button type="button" action="follow">follow</button> <button type="button" action="getNotifs">get notifs</button> <button type="button" action="starterPack">add to starter pack</button> <button type="button" action="list">add to list</button>
+
<div class="profileInfo">
+
<div class="displayName">{$user->displayName}</div>
+
<div class="handle">@{$user->handle}</div>
+
<div class="did">{$user->did}</div>
+
<div class="description">
+
{$user->description}
+
</div>
+
<div class="userStats">
+
</div>
+
<div class="actions">
+
<button type="button" action="follow">follow</button> <button type="button" action="getNotifs">get notifs</button> <button type="button" action="starterPack">add to starter pack</button> <button type="button" action="list">add to list</button>
+
</div>
</div>
</div>
-
</div>
-
<div class="postsList">
-
<a href="#" class="refreshBar" data-feed="author">check for new posts</a>
-
{if $posts}
-
{foreach $posts as $post}
-
{include 'post.latte', post: $post}
-
{/foreach}
-
{/if}
-
</div>
+
<div class="postsList">
+
<a href="#" class="refreshBar" data-feed="author">check for new posts</a>
+
{include '_partials/feedPosts.latte', posts: $posts}
+
</div>
+
{/block}
+18 -12
templates/search.latte
···
-
<form class="searchForm" method="POST">
-
<div class="form-input">
-
<label>Query</label>
-
<p class="help">Search is limited to this instance's flock of PDSes!</p>
-
<input type="text" name="search" id="search" />
-
</div>
-
<div class="form-input btn-row">
-
<button type="submit" class="btn btn-primary">Search</button>
-
</div>
-
</form>
-
<div id="results">
+
{layout 'layout.latte'}
+
+
{block title} | search{/block}
-
</div>
+
{block content}
+
<form class="searchForm" method="POST">
+
<div class="form-input">
+
<label>Query</label>
+
<p class="help">Search is limited to this instance's flock of PDSes!</p>
+
<input type="text" name="search" id="search" />
+
</div>
+
<div class="form-input btn-row">
+
<button type="submit" class="btn btn-primary">Search</button>
+
</div>
+
</form>
+
<div id="results">
+
+
</div>
+
{/block}
+2
templates/settings.latte
···
{layout 'layout.latte'}
+
{block title} | settings{/block}
+
{block content}
<h2>Settings</h2>
At some point settings will go here!
+33 -13
templates/single.latte
···
{layout 'layout.latte'}
-
{include '_partials/post.latte', post: $post}
+
{block title} | post by {$post->displayName} (@{$post->handle}){/block}
+
+
{block content}
+
{include '_partials/post.latte', post: $post}
<div id="interactions">
<button type="button" popovertarget="likes" class="btn-invis">See Likes</button> <button type="button" popovertarget="reposts" class="btn-invis">See Reposts</button> <button type="button" popovertarget="quotes" class="btn-invis">See Quotes</button>
</div>
<div id="likes" popover>
-
{foreach $post->likes as $like}
-
{include 'profile_mini.latte', user: $like}
-
{/foreach}
+
<div class="inner">
+
<div n:if="count($likes) === 0">
+
<p>No reposts yet!</p>
+
</div>
+
{foreach $likes as $like}
+
{include '_partials/profile_mini.latte', displayName: $like->actor->displayName, handle: $like->actor->handle, did: $like->actor->did, avatar: $like->actor->avatar, profileLink: '/u/'.$like->actor->handle}
+
{/foreach}
+
</div>
</div>
<div id="reposts" popover>
-
{foreach $post->reposts as $repost}
-
{include 'profile_mini.latte', user: $repost}
-
{/foreach}
+
<div class="inner">
+
<div n:if="count($reposts) === 0">
+
<p>No reposts yet!</p>
+
</div>
+
{foreach $reposts as $repost}
+
{include '_partials/profile_mini.latte', displayName: $repost->actor->displayName, handle: $repost->actor->handle, did: $repost->actor->did, avatar: $repost->actor->avatar, profileLink: '/u/'.$repost->actor->handle}
+
{/foreach}
+
</div>
</div>
<div id="quotes" popover>
-
{foreach $post->quotes as $quote}
-
{include 'post.latte', post: $quote}
-
{/foreach}
+
<div class="inner">
+
<div n:if="count($quotes) === 0">
+
<p>No quotes yet!</p>
+
</div>
+
{foreach $quotes as $quote}
+
{include '_partials/post.latte', post: $quote}
+
{/foreach}
+
</div>
</div>
<div id="replies">
-
{foreach $post->replies as $reply}
-
{include 'post.latte', post: $reply}
+
<h2>Replies</h2>
+
{foreach $replies as $reply}
+
{include '_partials/post.latte', post: $reply}
{/foreach}
-
</div>
+
</div>
+
{/block}
+2
vendor/composer/autoload_files.php
···
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'6594cffae477af99d531f0d3d4c9e533' => $vendorDir . '/adhocore/cli/src/functions.php',
'4cdafd4a5191caf078235e7dd119fdaf' => $vendorDir . '/flightphp/core/flight/autoload.php',
+
'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'd507e002f7fce7f0c6dbf1f22edcb902' => $vendorDir . '/tracy/tracy/src/Tracy/functions.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
+
'c4e03ecd470d2a87804979c0a8152284' => $vendorDir . '/react/async/src/functions_include.php',
);
+3
vendor/composer/autoload_psr4.php
···
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'Symfony\\Component\\DomCrawler\\' => array($vendorDir . '/symfony/dom-crawler'),
'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'),
+
'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
+
'React\\EventLoop\\' => array($vendorDir . '/react/event-loop/src'),
+
'React\\Async\\' => array($vendorDir . '/react/async/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
+20
vendor/composer/autoload_static.php
···
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'6594cffae477af99d531f0d3d4c9e533' => __DIR__ . '/..' . '/adhocore/cli/src/functions.php',
'4cdafd4a5191caf078235e7dd119fdaf' => __DIR__ . '/..' . '/flightphp/core/flight/autoload.php',
+
'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'd507e002f7fce7f0c6dbf1f22edcb902' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/functions.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
+
'c4e03ecd470d2a87804979c0a8152284' => __DIR__ . '/..' . '/react/async/src/functions_include.php',
);
public static $prefixLengthsPsr4 = array (
···
'Symfony\\Polyfill\\Ctype\\' => 23,
'Symfony\\Component\\DomCrawler\\' => 29,
'Symfony\\Component\\CssSelector\\' => 30,
+
),
+
'R' =>
+
array (
+
'React\\Promise\\' => 14,
+
'React\\EventLoop\\' => 16,
+
'React\\Async\\' => 12,
),
'P' =>
array (
···
'Symfony\\Component\\CssSelector\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/css-selector',
+
),
+
'React\\Promise\\' =>
+
array (
+
0 => __DIR__ . '/..' . '/react/promise/src',
+
),
+
'React\\EventLoop\\' =>
+
array (
+
0 => __DIR__ . '/..' . '/react/event-loop/src',
+
),
+
'React\\Async\\' =>
+
array (
+
0 => __DIR__ . '/..' . '/react/async/src',
),
'Psr\\Log\\' =>
array (
+229
vendor/composer/installed.json
···
"install-path": "../ralouphie/getallheaders"
},
+
"name": "react/async",
+
"version": "v4.3.0",
+
"version_normalized": "4.3.0.0",
+
"source": {
+
"type": "git",
+
"url": "https://github.com/reactphp/async.git",
+
"reference": "635d50e30844a484495713e8cb8d9e079c0008a5"
+
},
+
"dist": {
+
"type": "zip",
+
"url": "https://api.github.com/repos/reactphp/async/zipball/635d50e30844a484495713e8cb8d9e079c0008a5",
+
"reference": "635d50e30844a484495713e8cb8d9e079c0008a5",
+
"shasum": ""
+
},
+
"require": {
+
"php": ">=8.1",
+
"react/event-loop": "^1.2",
+
"react/promise": "^3.2 || ^2.8 || ^1.2.1"
+
},
+
"require-dev": {
+
"phpstan/phpstan": "1.10.39",
+
"phpunit/phpunit": "^9.6"
+
},
+
"time": "2024-06-04T14:40:02+00:00",
+
"type": "library",
+
"installation-source": "dist",
+
"autoload": {
+
"files": [
+
"src/functions_include.php"
+
],
+
"psr-4": {
+
"React\\Async\\": "src/"
+
}
+
},
+
"notification-url": "https://packagist.org/downloads/",
+
"license": [
+
"MIT"
+
],
+
"authors": [
+
{
+
"name": "Christian Lück",
+
"email": "christian@clue.engineering",
+
"homepage": "https://clue.engineering/"
+
},
+
{
+
"name": "Cees-Jan Kiewiet",
+
"email": "reactphp@ceesjankiewiet.nl",
+
"homepage": "https://wyrihaximus.net/"
+
},
+
{
+
"name": "Jan Sorgalla",
+
"email": "jsorgalla@gmail.com",
+
"homepage": "https://sorgalla.com/"
+
},
+
{
+
"name": "Chris Boden",
+
"email": "cboden@gmail.com",
+
"homepage": "https://cboden.dev/"
+
}
+
],
+
"description": "Async utilities and fibers for ReactPHP",
+
"keywords": [
+
"async",
+
"reactphp"
+
],
+
"support": {
+
"issues": "https://github.com/reactphp/async/issues",
+
"source": "https://github.com/reactphp/async/tree/v4.3.0"
+
},
+
"funding": [
+
{
+
"url": "https://opencollective.com/reactphp",
+
"type": "open_collective"
+
}
+
],
+
"install-path": "../react/async"
+
},
+
{
+
"name": "react/event-loop",
+
"version": "v1.5.0",
+
"version_normalized": "1.5.0.0",
+
"source": {
+
"type": "git",
+
"url": "https://github.com/reactphp/event-loop.git",
+
"reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354"
+
},
+
"dist": {
+
"type": "zip",
+
"url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354",
+
"reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354",
+
"shasum": ""
+
},
+
"require": {
+
"php": ">=5.3.0"
+
},
+
"require-dev": {
+
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+
},
+
"suggest": {
+
"ext-pcntl": "For signal handling support when using the StreamSelectLoop"
+
},
+
"time": "2023-11-13T13:48:05+00:00",
+
"type": "library",
+
"installation-source": "dist",
+
"autoload": {
+
"psr-4": {
+
"React\\EventLoop\\": "src/"
+
}
+
},
+
"notification-url": "https://packagist.org/downloads/",
+
"license": [
+
"MIT"
+
],
+
"authors": [
+
{
+
"name": "Christian Lück",
+
"email": "christian@clue.engineering",
+
"homepage": "https://clue.engineering/"
+
},
+
{
+
"name": "Cees-Jan Kiewiet",
+
"email": "reactphp@ceesjankiewiet.nl",
+
"homepage": "https://wyrihaximus.net/"
+
},
+
{
+
"name": "Jan Sorgalla",
+
"email": "jsorgalla@gmail.com",
+
"homepage": "https://sorgalla.com/"
+
},
+
{
+
"name": "Chris Boden",
+
"email": "cboden@gmail.com",
+
"homepage": "https://cboden.dev/"
+
}
+
],
+
"description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
+
"keywords": [
+
"asynchronous",
+
"event-loop"
+
],
+
"support": {
+
"issues": "https://github.com/reactphp/event-loop/issues",
+
"source": "https://github.com/reactphp/event-loop/tree/v1.5.0"
+
},
+
"funding": [
+
{
+
"url": "https://opencollective.com/reactphp",
+
"type": "open_collective"
+
}
+
],
+
"install-path": "../react/event-loop"
+
},
+
{
+
"name": "react/promise",
+
"version": "v3.3.0",
+
"version_normalized": "3.3.0.0",
+
"source": {
+
"type": "git",
+
"url": "https://github.com/reactphp/promise.git",
+
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a"
+
},
+
"dist": {
+
"type": "zip",
+
"url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a",
+
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a",
+
"shasum": ""
+
},
+
"require": {
+
"php": ">=7.1.0"
+
},
+
"require-dev": {
+
"phpstan/phpstan": "1.12.28 || 1.4.10",
+
"phpunit/phpunit": "^9.6 || ^7.5"
+
},
+
"time": "2025-08-19T18:57:03+00:00",
+
"type": "library",
+
"installation-source": "dist",
+
"autoload": {
+
"files": [
+
"src/functions_include.php"
+
],
+
"psr-4": {
+
"React\\Promise\\": "src/"
+
}
+
},
+
"notification-url": "https://packagist.org/downloads/",
+
"license": [
+
"MIT"
+
],
+
"authors": [
+
{
+
"name": "Jan Sorgalla",
+
"email": "jsorgalla@gmail.com",
+
"homepage": "https://sorgalla.com/"
+
},
+
{
+
"name": "Christian Lück",
+
"email": "christian@clue.engineering",
+
"homepage": "https://clue.engineering/"
+
},
+
{
+
"name": "Cees-Jan Kiewiet",
+
"email": "reactphp@ceesjankiewiet.nl",
+
"homepage": "https://wyrihaximus.net/"
+
},
+
{
+
"name": "Chris Boden",
+
"email": "cboden@gmail.com",
+
"homepage": "https://cboden.dev/"
+
}
+
],
+
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
+
"keywords": [
+
"promise",
+
"promises"
+
],
+
"support": {
+
"issues": "https://github.com/reactphp/promise/issues",
+
"source": "https://github.com/reactphp/promise/tree/v3.3.0"
+
},
+
"funding": [
+
{
+
"url": "https://opencollective.com/reactphp",
+
"type": "open_collective"
+
}
+
],
+
"install-path": "../react/promise"
+
},
+
{
"name": "symfony/css-selector",
"version": "v7.3.0",
"version_normalized": "7.3.0.0",
+29 -2
vendor/composer/installed.php
···
'name' => '__root__',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
-
'reference' => '17a2b5d2e68ab0da5b500ef4ae0fc450b64a0b69',
+
'reference' => 'bea8476a6fd638d67046c875c6410c586ef4f117',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
···
'__root__' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
-
'reference' => '17a2b5d2e68ab0da5b500ef4ae0fc450b64a0b69',
+
'reference' => 'bea8476a6fd638d67046c875c6410c586ef4f117',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
···
'reference' => '120b605dfeb996808c31b6477290a714d356e822',
'type' => 'library',
'install_path' => __DIR__ . '/../ralouphie/getallheaders',
+
'aliases' => array(),
+
'dev_requirement' => false,
+
),
+
'react/async' => array(
+
'pretty_version' => 'v4.3.0',
+
'version' => '4.3.0.0',
+
'reference' => '635d50e30844a484495713e8cb8d9e079c0008a5',
+
'type' => 'library',
+
'install_path' => __DIR__ . '/../react/async',
+
'aliases' => array(),
+
'dev_requirement' => false,
+
),
+
'react/event-loop' => array(
+
'pretty_version' => 'v1.5.0',
+
'version' => '1.5.0.0',
+
'reference' => 'bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354',
+
'type' => 'library',
+
'install_path' => __DIR__ . '/../react/event-loop',
+
'aliases' => array(),
+
'dev_requirement' => false,
+
),
+
'react/promise' => array(
+
'pretty_version' => 'v3.3.0',
+
'version' => '3.3.0.0',
+
'reference' => '23444f53a813a3296c1368bb104793ce8d88f04a',
+
'type' => 'library',
+
'install_path' => __DIR__ . '/../react/promise',
'aliases' => array(),
'dev_requirement' => false,
),
+112
vendor/react/async/CHANGELOG.md
···
+
# Changelog
+
+
## 4.3.0 (2024-06-04)
+
+
* Feature: Improve performance by avoiding unneeded references in `FiberMap`.
+
(#88 by @clue)
+
+
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
+
(#87 by @clue)
+
+
* Improve type safety for test environment.
+
(#86 by @SimonFrings)
+
+
## 4.2.0 (2023-11-22)
+
+
* Feature: Add Promise v3 template types for all public functions.
+
(#40 by @WyriHaximus and @clue)
+
+
All our public APIs now use Promise v3 template types to guide IDEs and static
+
analysis tools (like PHPStan), helping with proper type usage and improving
+
code quality:
+
+
```php
+
assertType('bool', await(resolve(true)));
+
assertType('PromiseInterface<bool>', async(fn(): bool => true)());
+
assertType('PromiseInterface<bool>', coroutine(fn(): bool => true));
+
```
+
+
* Feature: Full PHP 8.3 compatibility.
+
(#81 by @clue)
+
+
* Update test suite to avoid unhandled promise rejections.
+
(#79 by @clue)
+
+
## 4.1.0 (2023-06-22)
+
+
* Feature: Add new `delay()` function to delay program execution.
+
(#69 and #78 by @clue)
+
+
```php
+
echo 'a';
+
Loop::addTimer(1.0, function () {
+
echo 'b';
+
});
+
React\Async\delay(3.0);
+
echo 'c';
+
+
// prints "a" at t=0.0s
+
// prints "b" at t=1.0s
+
// prints "c" at t=3.0s
+
```
+
+
* Update test suite, add PHPStan with `max` level and report failed assertions.
+
(#66 and #76 by @clue and #61 and #73 by @WyriHaximus)
+
+
## 4.0.0 (2022-07-11)
+
+
A major new feature release, see [**release announcement**](https://clue.engineering/2022/announcing-reactphp-async).
+
+
* We'd like to emphasize that this component is production ready and battle-tested.
+
We plan to support all long-term support (LTS) releases for at least 24 months,
+
so you have a rock-solid foundation to build on top of.
+
+
* The v4 release will be the way forward for this package. However, we will still
+
actively support v3 and v2 to provide a smooth upgrade path for those not yet
+
on PHP 8.1+. If you're using an older PHP version, you may use either version
+
which all provide a compatible API but may not take advantage of newer language
+
features. You may target multiple versions at the same time to support a wider range of
+
PHP versions:
+
+
* [`4.x` branch](https://github.com/reactphp/async/tree/4.x) (PHP 8.1+)
+
* [`3.x` branch](https://github.com/reactphp/async/tree/3.x) (PHP 7.1+)
+
* [`2.x` branch](https://github.com/reactphp/async/tree/2.x) (PHP 5.3+)
+
+
This update involves some major new features and a minor BC break over the
+
`v3.0.0` release. We've tried hard to avoid BC breaks where possible and
+
minimize impact otherwise. We expect that most consumers of this package will be
+
affected by BC breaks, but updating should take no longer than a few minutes.
+
See below for more details:
+
+
* Feature / BC break: Require PHP 8.1+ and add `mixed` type declarations.
+
(#14 by @clue)
+
+
* Feature: Add Fiber-based `async()` and `await()` functions.
+
(#15, #18, #19 and #20 by @WyriHaximus and #26, #28, #30, #32, #34, #55 and #57 by @clue)
+
+
* Project maintenance, rename `main` branch to `4.x` and update installation instructions.
+
(#29 by @clue)
+
+
The following changes had to be ported to this release due to our branching
+
strategy, but also appeared in the `v3.0.0` release:
+
+
* Feature: Support iterable type for `parallel()` + `series()` + `waterfall()`.
+
(#49 by @clue)
+
+
* Feature: Forward compatibility with upcoming Promise v3.
+
(#48 by @clue)
+
+
* Minor documentation improvements.
+
(#36 by @SimonFrings and #51 by @nhedger)
+
+
## 3.0.0 (2022-07-11)
+
+
See [`3.x` CHANGELOG](https://github.com/reactphp/async/blob/3.x/CHANGELOG.md) for more details.
+
+
## 2.0.0 (2022-07-11)
+
+
See [`2.x` CHANGELOG](https://github.com/reactphp/async/blob/2.x/CHANGELOG.md) for more details.
+
+
## 1.0.0 (2013-02-07)
+
+
* First tagged release
+19
vendor/react/async/LICENSE
···
+
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
+
+
Permission is hereby granted, free of charge, to any person obtaining a copy
+
of this software and associated documentation files (the "Software"), to deal
+
in the Software without restriction, including without limitation the rights
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+
copies of the Software, and to permit persons to whom the Software is furnished
+
to do so, subject to the following conditions:
+
+
The above copyright notice and this permission notice shall be included in all
+
copies or substantial portions of the Software.
+
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+
THE SOFTWARE.
+672
vendor/react/async/README.md
···
+
# Async Utilities
+
+
[![CI status](https://github.com/reactphp/async/workflows/CI/badge.svg)](https://github.com/reactphp/async/actions)
+
[![installs on Packagist](https://img.shields.io/packagist/dt/react/async?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/async)
+
+
Async utilities and fibers for [ReactPHP](https://reactphp.org/).
+
+
This library allows you to manage async control flow. It provides a number of
+
combinators for [Promise](https://github.com/reactphp/promise)-based APIs.
+
Instead of nesting or chaining promise callbacks, you can declare them as a
+
list, which is resolved sequentially in an async manner.
+
React/Async will not automagically change blocking code to be async. You need
+
to have an actual event loop and non-blocking libraries interacting with that
+
event loop for it to work. As long as you have a Promise-based API that runs in
+
an event loop, it can be used with this library.
+
+
**Table of Contents**
+
+
* [Usage](#usage)
+
* [async()](#async)
+
* [await()](#await)
+
* [coroutine()](#coroutine)
+
* [delay()](#delay)
+
* [parallel()](#parallel)
+
* [series()](#series)
+
* [waterfall()](#waterfall)
+
* [Todo](#todo)
+
* [Install](#install)
+
* [Tests](#tests)
+
* [License](#license)
+
+
## Usage
+
+
This lightweight library consists only of a few simple functions.
+
All functions reside under the `React\Async` namespace.
+
+
The below examples refer to all functions with their fully-qualified names like this:
+
+
```php
+
React\Async\await(…);
+
```
+
+
As of PHP 5.6+ you can also import each required function into your code like this:
+
+
```php
+
use function React\Async\await;
+
+
await(…);
+
```
+
+
Alternatively, you can also use an import statement similar to this:
+
+
```php
+
use React\Async;
+
+
Async\await(…);
+
```
+
+
### async()
+
+
The `async(callable():(PromiseInterface<T>|T) $function): (callable():PromiseInterface<T>)` function can be used to
+
return an async function for a function that uses [`await()`](#await) internally.
+
+
This function is specifically designed to complement the [`await()` function](#await).
+
The [`await()` function](#await) can be considered *blocking* from the
+
perspective of the calling code. You can avoid this blocking behavior by
+
wrapping it in an `async()` function call. Everything inside this function
+
will still be blocked, but everything outside this function can be executed
+
asynchronously without blocking:
+
+
```php
+
Loop::addTimer(0.5, React\Async\async(function () {
+
echo 'a';
+
React\Async\await(React\Promise\Timer\sleep(1.0));
+
echo 'c';
+
}));
+
+
Loop::addTimer(1.0, function () {
+
echo 'b';
+
});
+
+
// prints "a" at t=0.5s
+
// prints "b" at t=1.0s
+
// prints "c" at t=1.5s
+
```
+
+
See also the [`await()` function](#await) for more details.
+
+
Note that this function only works in tandem with the [`await()` function](#await).
+
In particular, this function does not "magically" make any blocking function
+
non-blocking:
+
+
```php
+
Loop::addTimer(0.5, React\Async\async(function () {
+
echo 'a';
+
sleep(1); // broken: using PHP's blocking sleep() for demonstration purposes
+
echo 'c';
+
}));
+
+
Loop::addTimer(1.0, function () {
+
echo 'b';
+
});
+
+
// prints "a" at t=0.5s
+
// prints "c" at t=1.5s: Correct timing, but wrong order
+
// prints "b" at t=1.5s: Triggered too late because it was blocked
+
```
+
+
As an alternative, you should always make sure to use this function in tandem
+
with the [`await()` function](#await) and an async API returning a promise
+
as shown in the previous example.
+
+
The `async()` function is specifically designed for cases where it is used
+
as a callback (such as an event loop timer, event listener, or promise
+
callback). For this reason, it returns a new function wrapping the given
+
`$function` instead of directly invoking it and returning its value.
+
+
```php
+
use function React\Async\async;
+
+
Loop::addTimer(1.0, async(function () { … }));
+
$connection->on('close', async(function () { … }));
+
$stream->on('data', async(function ($data) { … }));
+
$promise->then(async(function (int $result) { … }));
+
```
+
+
You can invoke this wrapping function to invoke the given `$function` with
+
any arguments given as-is. The function will always return a Promise which
+
will be fulfilled with whatever your `$function` returns. Likewise, it will
+
return a promise that will be rejected if you throw an `Exception` or
+
`Throwable` from your `$function`. This allows you to easily create
+
Promise-based functions:
+
+
```php
+
$promise = React\Async\async(function (): int {
+
$browser = new React\Http\Browser();
+
$urls = [
+
'https://example.com/alice',
+
'https://example.com/bob'
+
];
+
+
$bytes = 0;
+
foreach ($urls as $url) {
+
$response = React\Async\await($browser->get($url));
+
assert($response instanceof Psr\Http\Message\ResponseInterface);
+
$bytes += $response->getBody()->getSize();
+
}
+
return $bytes;
+
})();
+
+
$promise->then(function (int $bytes) {
+
echo 'Total size: ' . $bytes . PHP_EOL;
+
}, function (Exception $e) {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
});
+
```
+
+
The previous example uses [`await()`](#await) inside a loop to highlight how
+
this vastly simplifies consuming asynchronous operations. At the same time,
+
this naive example does not leverage concurrent execution, as it will
+
essentially "await" between each operation. In order to take advantage of
+
concurrent execution within the given `$function`, you can "await" multiple
+
promises by using a single [`await()`](#await) together with Promise-based
+
primitives like this:
+
+
```php
+
$promise = React\Async\async(function (): int {
+
$browser = new React\Http\Browser();
+
$urls = [
+
'https://example.com/alice',
+
'https://example.com/bob'
+
];
+
+
$promises = [];
+
foreach ($urls as $url) {
+
$promises[] = $browser->get($url);
+
}
+
+
try {
+
$responses = React\Async\await(React\Promise\all($promises));
+
} catch (Exception $e) {
+
foreach ($promises as $promise) {
+
$promise->cancel();
+
}
+
throw $e;
+
}
+
+
$bytes = 0;
+
foreach ($responses as $response) {
+
assert($response instanceof Psr\Http\Message\ResponseInterface);
+
$bytes += $response->getBody()->getSize();
+
}
+
return $bytes;
+
})();
+
+
$promise->then(function (int $bytes) {
+
echo 'Total size: ' . $bytes . PHP_EOL;
+
}, function (Exception $e) {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
});
+
```
+
+
The returned promise is implemented in such a way that it can be cancelled
+
when it is still pending. Cancelling a pending promise will cancel any awaited
+
promises inside that fiber or any nested fibers. As such, the following example
+
will only output `ab` and cancel the pending [`delay()`](#delay).
+
The [`await()`](#await) calls in this example would throw a `RuntimeException`
+
from the cancelled [`delay()`](#delay) call that bubbles up through the fibers.
+
+
```php
+
$promise = async(static function (): int {
+
echo 'a';
+
await(async(static function (): void {
+
echo 'b';
+
delay(2);
+
echo 'c';
+
})());
+
echo 'd';
+
+
return time();
+
})();
+
+
$promise->cancel();
+
await($promise);
+
```
+
+
### await()
+
+
The `await(PromiseInterface<T> $promise): T` function can be used to
+
block waiting for the given `$promise` to be fulfilled.
+
+
```php
+
$result = React\Async\await($promise);
+
```
+
+
This function will only return after the given `$promise` has settled, i.e.
+
either fulfilled or rejected. While the promise is pending, this function
+
can be considered *blocking* from the perspective of the calling code.
+
You can avoid this blocking behavior by wrapping it in an [`async()` function](#async)
+
call. Everything inside this function will still be blocked, but everything
+
outside this function can be executed asynchronously without blocking:
+
+
```php
+
Loop::addTimer(0.5, React\Async\async(function () {
+
echo 'a';
+
React\Async\await(React\Promise\Timer\sleep(1.0));
+
echo 'c';
+
}));
+
+
Loop::addTimer(1.0, function () {
+
echo 'b';
+
});
+
+
// prints "a" at t=0.5s
+
// prints "b" at t=1.0s
+
// prints "c" at t=1.5s
+
```
+
+
See also the [`async()` function](#async) for more details.
+
+
Once the promise is fulfilled, this function will return whatever the promise
+
resolved to.
+
+
Once the promise is rejected, this will throw whatever the promise rejected
+
with. If the promise did not reject with an `Exception` or `Throwable`, then
+
this function will throw an `UnexpectedValueException` instead.
+
+
```php
+
try {
+
$result = React\Async\await($promise);
+
// promise successfully fulfilled with $result
+
echo 'Result: ' . $result;
+
} catch (Throwable $e) {
+
// promise rejected with $e
+
echo 'Error: ' . $e->getMessage();
+
}
+
```
+
+
### coroutine()
+
+
The `coroutine(callable(mixed ...$args):(\Generator|PromiseInterface<T>|T) $function, mixed ...$args): PromiseInterface<T>` function can be used to
+
execute a Generator-based coroutine to "await" promises.
+
+
```php
+
React\Async\coroutine(function () {
+
$browser = new React\Http\Browser();
+
+
try {
+
$response = yield $browser->get('https://example.com/');
+
assert($response instanceof Psr\Http\Message\ResponseInterface);
+
echo $response->getBody();
+
} catch (Exception $e) {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
}
+
});
+
```
+
+
Using Generator-based coroutines is an alternative to directly using the
+
underlying promise APIs. For many use cases, this makes using promise-based
+
APIs much simpler, as it resembles a synchronous code flow more closely.
+
The above example performs the equivalent of directly using the promise APIs:
+
+
```php
+
$browser = new React\Http\Browser();
+
+
$browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
+
echo $response->getBody();
+
}, function (Exception $e) {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
});
+
```
+
+
The `yield` keyword can be used to "await" a promise resolution. Internally,
+
it will turn the entire given `$function` into a [`Generator`](https://www.php.net/manual/en/class.generator.php).
+
This allows the execution to be interrupted and resumed at the same place
+
when the promise is fulfilled. The `yield` statement returns whatever the
+
promise is fulfilled with. If the promise is rejected, it will throw an
+
`Exception` or `Throwable`.
+
+
The `coroutine()` function will always return a Promise which will be
+
fulfilled with whatever your `$function` returns. Likewise, it will return
+
a promise that will be rejected if you throw an `Exception` or `Throwable`
+
from your `$function`. This allows you to easily create Promise-based
+
functions:
+
+
```php
+
$promise = React\Async\coroutine(function () {
+
$browser = new React\Http\Browser();
+
$urls = [
+
'https://example.com/alice',
+
'https://example.com/bob'
+
];
+
+
$bytes = 0;
+
foreach ($urls as $url) {
+
$response = yield $browser->get($url);
+
assert($response instanceof Psr\Http\Message\ResponseInterface);
+
$bytes += $response->getBody()->getSize();
+
}
+
return $bytes;
+
});
+
+
$promise->then(function (int $bytes) {
+
echo 'Total size: ' . $bytes . PHP_EOL;
+
}, function (Exception $e) {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
});
+
```
+
+
The previous example uses a `yield` statement inside a loop to highlight how
+
this vastly simplifies consuming asynchronous operations. At the same time,
+
this naive example does not leverage concurrent execution, as it will
+
essentially "await" between each operation. In order to take advantage of
+
concurrent execution within the given `$function`, you can "await" multiple
+
promises by using a single `yield` together with Promise-based primitives
+
like this:
+
+
```php
+
$promise = React\Async\coroutine(function () {
+
$browser = new React\Http\Browser();
+
$urls = [
+
'https://example.com/alice',
+
'https://example.com/bob'
+
];
+
+
$promises = [];
+
foreach ($urls as $url) {
+
$promises[] = $browser->get($url);
+
}
+
+
try {
+
$responses = yield React\Promise\all($promises);
+
} catch (Exception $e) {
+
foreach ($promises as $promise) {
+
$promise->cancel();
+
}
+
throw $e;
+
}
+
+
$bytes = 0;
+
foreach ($responses as $response) {
+
assert($response instanceof Psr\Http\Message\ResponseInterface);
+
$bytes += $response->getBody()->getSize();
+
}
+
return $bytes;
+
});
+
+
$promise->then(function (int $bytes) {
+
echo 'Total size: ' . $bytes . PHP_EOL;
+
}, function (Exception $e) {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
});
+
```
+
+
### delay()
+
+
The `delay(float $seconds): void` function can be used to
+
delay program execution for duration given in `$seconds`.
+
+
```php
+
React\Async\delay($seconds);
+
```
+
+
This function will only return after the given number of `$seconds` have
+
elapsed. If there are no other events attached to this loop, it will behave
+
similar to PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php).
+
+
```php
+
echo 'a';
+
React\Async\delay(1.0);
+
echo 'b';
+
+
// prints "a" at t=0.0s
+
// prints "b" at t=1.0s
+
```
+
+
Unlike PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php),
+
this function may not necessarily halt execution of the entire process thread.
+
Instead, it allows the event loop to run any other events attached to the
+
same loop until the delay returns:
+
+
```php
+
echo 'a';
+
Loop::addTimer(1.0, function (): void {
+
echo 'b';
+
});
+
React\Async\delay(3.0);
+
echo 'c';
+
+
// prints "a" at t=0.0s
+
// prints "b" at t=1.0s
+
// prints "c" at t=3.0s
+
```
+
+
This behavior is especially useful if you want to delay the program execution
+
of a particular routine, such as when building a simple polling or retry
+
mechanism:
+
+
```php
+
try {
+
something();
+
} catch (Throwable) {
+
// in case of error, retry after a short delay
+
React\Async\delay(1.0);
+
something();
+
}
+
```
+
+
Because this function only returns after some time has passed, it can be
+
considered *blocking* from the perspective of the calling code. You can avoid
+
this blocking behavior by wrapping it in an [`async()` function](#async) call.
+
Everything inside this function will still be blocked, but everything outside
+
this function can be executed asynchronously without blocking:
+
+
```php
+
Loop::addTimer(0.5, React\Async\async(function (): void {
+
echo 'a';
+
React\Async\delay(1.0);
+
echo 'c';
+
}));
+
+
Loop::addTimer(1.0, function (): void {
+
echo 'b';
+
});
+
+
// prints "a" at t=0.5s
+
// prints "b" at t=1.0s
+
// prints "c" at t=1.5s
+
```
+
+
See also the [`async()` function](#async) for more details.
+
+
Internally, the `$seconds` argument will be used as a timer for the loop so that
+
it keeps running until this timer triggers. This implies that if you pass a
+
really small (or negative) value, it will still start a timer and will thus
+
trigger at the earliest possible time in the future.
+
+
The function is implemented in such a way that it can be cancelled when it is
+
running inside an [`async()` function](#async). Cancelling the resulting
+
promise will clean up any pending timers and throw a `RuntimeException` from
+
the pending delay which in turn would reject the resulting promise.
+
+
```php
+
$promise = async(function (): void {
+
echo 'a';
+
delay(3.0);
+
echo 'b';
+
})();
+
+
Loop::addTimer(2.0, function () use ($promise): void {
+
$promise->cancel();
+
});
+
+
// prints "a" at t=0.0s
+
// rejects $promise at t=2.0
+
// never prints "b"
+
```
+
+
### parallel()
+
+
The `parallel(iterable<callable():PromiseInterface<T>> $tasks): PromiseInterface<array<T>>` function can be used
+
like this:
+
+
```php
+
<?php
+
+
use React\EventLoop\Loop;
+
use React\Promise\Promise;
+
+
React\Async\parallel([
+
function () {
+
return new Promise(function ($resolve) {
+
Loop::addTimer(1, function () use ($resolve) {
+
$resolve('Slept for a whole second');
+
});
+
});
+
},
+
function () {
+
return new Promise(function ($resolve) {
+
Loop::addTimer(1, function () use ($resolve) {
+
$resolve('Slept for another whole second');
+
});
+
});
+
},
+
function () {
+
return new Promise(function ($resolve) {
+
Loop::addTimer(1, function () use ($resolve) {
+
$resolve('Slept for yet another whole second');
+
});
+
});
+
},
+
])->then(function (array $results) {
+
foreach ($results as $result) {
+
var_dump($result);
+
}
+
}, function (Exception $e) {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
});
+
```
+
+
### series()
+
+
The `series(iterable<callable():PromiseInterface<T>> $tasks): PromiseInterface<array<T>>` function can be used
+
like this:
+
+
```php
+
<?php
+
+
use React\EventLoop\Loop;
+
use React\Promise\Promise;
+
+
React\Async\series([
+
function () {
+
return new Promise(function ($resolve) {
+
Loop::addTimer(1, function () use ($resolve) {
+
$resolve('Slept for a whole second');
+
});
+
});
+
},
+
function () {
+
return new Promise(function ($resolve) {
+
Loop::addTimer(1, function () use ($resolve) {
+
$resolve('Slept for another whole second');
+
});
+
});
+
},
+
function () {
+
return new Promise(function ($resolve) {
+
Loop::addTimer(1, function () use ($resolve) {
+
$resolve('Slept for yet another whole second');
+
});
+
});
+
},
+
])->then(function (array $results) {
+
foreach ($results as $result) {
+
var_dump($result);
+
}
+
}, function (Exception $e) {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
});
+
```
+
+
### waterfall()
+
+
The `waterfall(iterable<callable(mixed=):PromiseInterface<T>> $tasks): PromiseInterface<T>` function can be used
+
like this:
+
+
```php
+
<?php
+
+
use React\EventLoop\Loop;
+
use React\Promise\Promise;
+
+
$addOne = function ($prev = 0) {
+
return new Promise(function ($resolve) use ($prev) {
+
Loop::addTimer(1, function () use ($prev, $resolve) {
+
$resolve($prev + 1);
+
});
+
});
+
};
+
+
React\Async\waterfall([
+
$addOne,
+
$addOne,
+
$addOne
+
])->then(function ($prev) {
+
echo "Final result is $prev\n";
+
}, function (Exception $e) {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
});
+
```
+
+
## Todo
+
+
* Implement queue()
+
+
## Install
+
+
The recommended way to install this library is [through Composer](https://getcomposer.org/).
+
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+
This project follows [SemVer](https://semver.org/).
+
This will install the latest supported version from this branch:
+
+
```bash
+
composer require react/async:^4.3
+
```
+
+
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+
This project aims to run on any platform and thus does not require any PHP
+
extensions and supports running on PHP 8.1+.
+
It's *highly recommended to use the latest supported PHP version* for this project.
+
+
We're committed to providing long-term support (LTS) options and to provide a
+
smooth upgrade path. If you're using an older PHP version, you may use the
+
[`3.x` branch](https://github.com/reactphp/async/tree/3.x) (PHP 7.1+) or
+
[`2.x` branch](https://github.com/reactphp/async/tree/2.x) (PHP 5.3+) which both
+
provide a compatible API but do not take advantage of newer language features.
+
You may target multiple versions at the same time to support a wider range of
+
PHP versions like this:
+
+
```bash
+
composer require "react/async:^4 || ^3 || ^2"
+
```
+
+
## Tests
+
+
To run the test suite, you first need to clone this repo and then install all
+
dependencies [through Composer](https://getcomposer.org/):
+
+
```bash
+
composer install
+
```
+
+
To run the test suite, go to the project root and run:
+
+
```bash
+
vendor/bin/phpunit
+
```
+
+
On top of this, we use PHPStan on max level to ensure type safety across the project:
+
+
```bash
+
vendor/bin/phpstan
+
```
+
+
## License
+
+
MIT, see [LICENSE file](LICENSE).
+
+
This project is heavily influenced by [async.js](https://github.com/caolan/async).
+50
vendor/react/async/composer.json
···
+
{
+
"name": "react/async",
+
"description": "Async utilities and fibers for ReactPHP",
+
"keywords": ["async", "ReactPHP"],
+
"license": "MIT",
+
"authors": [
+
{
+
"name": "Christian Lück",
+
"homepage": "https://clue.engineering/",
+
"email": "christian@clue.engineering"
+
},
+
{
+
"name": "Cees-Jan Kiewiet",
+
"homepage": "https://wyrihaximus.net/",
+
"email": "reactphp@ceesjankiewiet.nl"
+
},
+
{
+
"name": "Jan Sorgalla",
+
"homepage": "https://sorgalla.com/",
+
"email": "jsorgalla@gmail.com"
+
},
+
{
+
"name": "Chris Boden",
+
"homepage": "https://cboden.dev/",
+
"email": "cboden@gmail.com"
+
}
+
],
+
"require": {
+
"php": ">=8.1",
+
"react/event-loop": "^1.2",
+
"react/promise": "^3.2 || ^2.8 || ^1.2.1"
+
},
+
"require-dev": {
+
"phpstan/phpstan": "1.10.39",
+
"phpunit/phpunit": "^9.6"
+
},
+
"autoload": {
+
"psr-4": {
+
"React\\Async\\": "src/"
+
},
+
"files": [
+
"src/functions_include.php"
+
]
+
},
+
"autoload-dev": {
+
"psr-4": {
+
"React\\Tests\\Async\\": "tests/"
+
}
+
}
+
}
+33
vendor/react/async/src/FiberFactory.php
···
+
<?php
+
+
namespace React\Async;
+
+
/**
+
* This factory its only purpose is interoperability. Where with
+
* event loops one could simply wrap another event loop. But with fibers
+
* that has become impossible and as such we provide this factory and the
+
* FiberInterface.
+
*
+
* Usage is not documented and as such not supported and might chang without
+
* notice. Use at your own risk.
+
*
+
* @internal
+
*/
+
final class FiberFactory
+
{
+
private static ?\Closure $factory = null;
+
+
public static function create(): FiberInterface
+
{
+
return (self::factory())();
+
}
+
+
public static function factory(?\Closure $factory = null): \Closure
+
{
+
if ($factory !== null) {
+
self::$factory = $factory;
+
}
+
+
return self::$factory ?? static fn (): FiberInterface => new SimpleFiber();
+
}
+
}
+23
vendor/react/async/src/FiberInterface.php
···
+
<?php
+
+
namespace React\Async;
+
+
/**
+
* This interface its only purpose is interoperability. Where with
+
* event loops one could simply wrap another event loop. But with fibers
+
* that has become impossible and as such we provide this interface and the
+
* FiberFactory.
+
*
+
* Usage is not documented and as such not supported and might chang without
+
* notice. Use at your own risk.
+
*
+
* @internal
+
*/
+
interface FiberInterface
+
{
+
public function resume(mixed $value): void;
+
+
public function throw(\Throwable $throwable): void;
+
+
public function suspend(): mixed;
+
}
+42
vendor/react/async/src/FiberMap.php
···
+
<?php
+
+
namespace React\Async;
+
+
use React\Promise\PromiseInterface;
+
+
/**
+
* @internal
+
*
+
* @template T
+
*/
+
final class FiberMap
+
{
+
/** @var array<int,PromiseInterface<T>> */
+
private static array $map = [];
+
+
/**
+
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
+
* @param PromiseInterface<T> $promise
+
*/
+
public static function setPromise(\Fiber $fiber, PromiseInterface $promise): void
+
{
+
self::$map[\spl_object_id($fiber)] = $promise;
+
}
+
+
/**
+
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
+
*/
+
public static function unsetPromise(\Fiber $fiber): void
+
{
+
unset(self::$map[\spl_object_id($fiber)]);
+
}
+
+
/**
+
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
+
* @return ?PromiseInterface<T>
+
*/
+
public static function getPromise(\Fiber $fiber): ?PromiseInterface
+
{
+
return self::$map[\spl_object_id($fiber)] ?? null;
+
}
+
}
+79
vendor/react/async/src/SimpleFiber.php
···
+
<?php
+
+
namespace React\Async;
+
+
use React\EventLoop\Loop;
+
+
/**
+
* @internal
+
*/
+
final class SimpleFiber implements FiberInterface
+
{
+
/** @var ?\Fiber<void,void,void,callable(): mixed> */
+
private static ?\Fiber $scheduler = null;
+
+
private static ?\Closure $suspend = null;
+
+
/** @var ?\Fiber<mixed,mixed,mixed,mixed> */
+
private ?\Fiber $fiber = null;
+
+
public function __construct()
+
{
+
$this->fiber = \Fiber::getCurrent();
+
}
+
+
public function resume(mixed $value): void
+
{
+
if ($this->fiber !== null) {
+
$this->fiber->resume($value);
+
} else {
+
self::$suspend = static fn() => $value;
+
}
+
+
if (self::$suspend !== null && \Fiber::getCurrent() === self::$scheduler) {
+
$suspend = self::$suspend;
+
self::$suspend = null;
+
+
\Fiber::suspend($suspend);
+
}
+
}
+
+
public function throw(\Throwable $throwable): void
+
{
+
if ($this->fiber !== null) {
+
$this->fiber->throw($throwable);
+
} else {
+
self::$suspend = static fn() => throw $throwable;
+
}
+
+
if (self::$suspend !== null && \Fiber::getCurrent() === self::$scheduler) {
+
$suspend = self::$suspend;
+
self::$suspend = null;
+
+
\Fiber::suspend($suspend);
+
}
+
}
+
+
public function suspend(): mixed
+
{
+
if ($this->fiber === null) {
+
if (self::$scheduler === null || self::$scheduler->isTerminated()) {
+
self::$scheduler = new \Fiber(static fn() => Loop::run());
+
// Run event loop to completion on shutdown.
+
\register_shutdown_function(static function (): void {
+
assert(self::$scheduler instanceof \Fiber);
+
if (self::$scheduler->isSuspended()) {
+
self::$scheduler->resume();
+
}
+
});
+
}
+
+
$ret = (self::$scheduler->isStarted() ? self::$scheduler->resume() : self::$scheduler->start());
+
assert(\is_callable($ret));
+
+
return $ret();
+
}
+
+
return \Fiber::suspend();
+
}
+
}
+846
vendor/react/async/src/functions.php
···
+
<?php
+
+
namespace React\Async;
+
+
use React\EventLoop\Loop;
+
use React\EventLoop\TimerInterface;
+
use React\Promise\Deferred;
+
use React\Promise\Promise;
+
use React\Promise\PromiseInterface;
+
use function React\Promise\reject;
+
use function React\Promise\resolve;
+
+
/**
+
* Return an async function for a function that uses [`await()`](#await) internally.
+
*
+
* This function is specifically designed to complement the [`await()` function](#await).
+
* The [`await()` function](#await) can be considered *blocking* from the
+
* perspective of the calling code. You can avoid this blocking behavior by
+
* wrapping it in an `async()` function call. Everything inside this function
+
* will still be blocked, but everything outside this function can be executed
+
* asynchronously without blocking:
+
*
+
* ```php
+
* Loop::addTimer(0.5, React\Async\async(function () {
+
* echo 'a';
+
* React\Async\await(React\Promise\Timer\sleep(1.0));
+
* echo 'c';
+
* }));
+
*
+
* Loop::addTimer(1.0, function () {
+
* echo 'b';
+
* });
+
*
+
* // prints "a" at t=0.5s
+
* // prints "b" at t=1.0s
+
* // prints "c" at t=1.5s
+
* ```
+
*
+
* See also the [`await()` function](#await) for more details.
+
*
+
* Note that this function only works in tandem with the [`await()` function](#await).
+
* In particular, this function does not "magically" make any blocking function
+
* non-blocking:
+
*
+
* ```php
+
* Loop::addTimer(0.5, React\Async\async(function () {
+
* echo 'a';
+
* sleep(1); // broken: using PHP's blocking sleep() for demonstration purposes
+
* echo 'c';
+
* }));
+
*
+
* Loop::addTimer(1.0, function () {
+
* echo 'b';
+
* });
+
*
+
* // prints "a" at t=0.5s
+
* // prints "c" at t=1.5s: Correct timing, but wrong order
+
* // prints "b" at t=1.5s: Triggered too late because it was blocked
+
* ```
+
*
+
* As an alternative, you should always make sure to use this function in tandem
+
* with the [`await()` function](#await) and an async API returning a promise
+
* as shown in the previous example.
+
*
+
* The `async()` function is specifically designed for cases where it is used
+
* as a callback (such as an event loop timer, event listener, or promise
+
* callback). For this reason, it returns a new function wrapping the given
+
* `$function` instead of directly invoking it and returning its value.
+
*
+
* ```php
+
* use function React\Async\async;
+
*
+
* Loop::addTimer(1.0, async(function () { … }));
+
* $connection->on('close', async(function () { … }));
+
* $stream->on('data', async(function ($data) { … }));
+
* $promise->then(async(function (int $result) { … }));
+
* ```
+
*
+
* You can invoke this wrapping function to invoke the given `$function` with
+
* any arguments given as-is. The function will always return a Promise which
+
* will be fulfilled with whatever your `$function` returns. Likewise, it will
+
* return a promise that will be rejected if you throw an `Exception` or
+
* `Throwable` from your `$function`. This allows you to easily create
+
* Promise-based functions:
+
*
+
* ```php
+
* $promise = React\Async\async(function (): int {
+
* $browser = new React\Http\Browser();
+
* $urls = [
+
* 'https://example.com/alice',
+
* 'https://example.com/bob'
+
* ];
+
*
+
* $bytes = 0;
+
* foreach ($urls as $url) {
+
* $response = React\Async\await($browser->get($url));
+
* assert($response instanceof Psr\Http\Message\ResponseInterface);
+
* $bytes += $response->getBody()->getSize();
+
* }
+
* return $bytes;
+
* })();
+
*
+
* $promise->then(function (int $bytes) {
+
* echo 'Total size: ' . $bytes . PHP_EOL;
+
* }, function (Exception $e) {
+
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
* });
+
* ```
+
*
+
* The previous example uses [`await()`](#await) inside a loop to highlight how
+
* this vastly simplifies consuming asynchronous operations. At the same time,
+
* this naive example does not leverage concurrent execution, as it will
+
* essentially "await" between each operation. In order to take advantage of
+
* concurrent execution within the given `$function`, you can "await" multiple
+
* promises by using a single [`await()`](#await) together with Promise-based
+
* primitives like this:
+
*
+
* ```php
+
* $promise = React\Async\async(function (): int {
+
* $browser = new React\Http\Browser();
+
* $urls = [
+
* 'https://example.com/alice',
+
* 'https://example.com/bob'
+
* ];
+
*
+
* $promises = [];
+
* foreach ($urls as $url) {
+
* $promises[] = $browser->get($url);
+
* }
+
*
+
* try {
+
* $responses = React\Async\await(React\Promise\all($promises));
+
* } catch (Exception $e) {
+
* foreach ($promises as $promise) {
+
* $promise->cancel();
+
* }
+
* throw $e;
+
* }
+
*
+
* $bytes = 0;
+
* foreach ($responses as $response) {
+
* assert($response instanceof Psr\Http\Message\ResponseInterface);
+
* $bytes += $response->getBody()->getSize();
+
* }
+
* return $bytes;
+
* })();
+
*
+
* $promise->then(function (int $bytes) {
+
* echo 'Total size: ' . $bytes . PHP_EOL;
+
* }, function (Exception $e) {
+
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
* });
+
* ```
+
*
+
* The returned promise is implemented in such a way that it can be cancelled
+
* when it is still pending. Cancelling a pending promise will cancel any awaited
+
* promises inside that fiber or any nested fibers. As such, the following example
+
* will only output `ab` and cancel the pending [`delay()`](#delay).
+
* The [`await()`](#await) calls in this example would throw a `RuntimeException`
+
* from the cancelled [`delay()`](#delay) call that bubbles up through the fibers.
+
*
+
* ```php
+
* $promise = async(static function (): int {
+
* echo 'a';
+
* await(async(static function (): void {
+
* echo 'b';
+
* delay(2);
+
* echo 'c';
+
* })());
+
* echo 'd';
+
*
+
* return time();
+
* })();
+
*
+
* $promise->cancel();
+
* await($promise);
+
* ```
+
*
+
* @template T
+
* @template A1 (any number of function arguments, see https://github.com/phpstan/phpstan/issues/8214)
+
* @template A2
+
* @template A3
+
* @template A4
+
* @template A5
+
* @param callable(A1,A2,A3,A4,A5): (PromiseInterface<T>|T) $function
+
* @return callable(A1=,A2=,A3=,A4=,A5=): PromiseInterface<T>
+
* @since 4.0.0
+
* @see coroutine()
+
*/
+
function async(callable $function): callable
+
{
+
return static function (mixed ...$args) use ($function): PromiseInterface {
+
$fiber = null;
+
/** @var PromiseInterface<T> $promise*/
+
$promise = new Promise(function (callable $resolve, callable $reject) use ($function, $args, &$fiber): void {
+
$fiber = new \Fiber(function () use ($resolve, $reject, $function, $args, &$fiber): void {
+
try {
+
$resolve($function(...$args));
+
} catch (\Throwable $exception) {
+
$reject($exception);
+
} finally {
+
assert($fiber instanceof \Fiber);
+
FiberMap::unsetPromise($fiber);
+
}
+
});
+
+
$fiber->start();
+
}, function () use (&$fiber): void {
+
assert($fiber instanceof \Fiber);
+
$promise = FiberMap::getPromise($fiber);
+
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
+
$promise->cancel();
+
}
+
});
+
+
$lowLevelFiber = \Fiber::getCurrent();
+
if ($lowLevelFiber !== null) {
+
FiberMap::setPromise($lowLevelFiber, $promise);
+
}
+
+
return $promise;
+
};
+
}
+
+
/**
+
* Block waiting for the given `$promise` to be fulfilled.
+
*
+
* ```php
+
* $result = React\Async\await($promise);
+
* ```
+
*
+
* This function will only return after the given `$promise` has settled, i.e.
+
* either fulfilled or rejected. While the promise is pending, this function
+
* can be considered *blocking* from the perspective of the calling code.
+
* You can avoid this blocking behavior by wrapping it in an [`async()` function](#async)
+
* call. Everything inside this function will still be blocked, but everything
+
* outside this function can be executed asynchronously without blocking:
+
*
+
* ```php
+
* Loop::addTimer(0.5, React\Async\async(function () {
+
* echo 'a';
+
* React\Async\await(React\Promise\Timer\sleep(1.0));
+
* echo 'c';
+
* }));
+
*
+
* Loop::addTimer(1.0, function () {
+
* echo 'b';
+
* });
+
*
+
* // prints "a" at t=0.5s
+
* // prints "b" at t=1.0s
+
* // prints "c" at t=1.5s
+
* ```
+
*
+
* See also the [`async()` function](#async) for more details.
+
*
+
* Once the promise is fulfilled, this function will return whatever the promise
+
* resolved to.
+
*
+
* Once the promise is rejected, this will throw whatever the promise rejected
+
* with. If the promise did not reject with an `Exception` or `Throwable`, then
+
* this function will throw an `UnexpectedValueException` instead.
+
*
+
* ```php
+
* try {
+
* $result = React\Async\await($promise);
+
* // promise successfully fulfilled with $result
+
* echo 'Result: ' . $result;
+
* } catch (Throwable $e) {
+
* // promise rejected with $e
+
* echo 'Error: ' . $e->getMessage();
+
* }
+
* ```
+
*
+
* @template T
+
* @param PromiseInterface<T> $promise
+
* @return T returns whatever the promise resolves to
+
* @throws \Exception when the promise is rejected with an `Exception`
+
* @throws \Throwable when the promise is rejected with a `Throwable`
+
* @throws \UnexpectedValueException when the promise is rejected with an unexpected value (Promise API v1 or v2 only)
+
*/
+
function await(PromiseInterface $promise): mixed
+
{
+
$fiber = null;
+
$resolved = false;
+
$rejected = false;
+
+
/** @var T $resolvedValue */
+
$resolvedValue = null;
+
$rejectedThrowable = null;
+
$lowLevelFiber = \Fiber::getCurrent();
+
+
$promise->then(
+
function (mixed $value) use (&$resolved, &$resolvedValue, &$fiber, $lowLevelFiber): void {
+
if ($lowLevelFiber !== null) {
+
FiberMap::unsetPromise($lowLevelFiber);
+
}
+
+
/** @var ?\Fiber<mixed,mixed,mixed,mixed> $fiber */
+
if ($fiber === null) {
+
$resolved = true;
+
/** @var T $resolvedValue */
+
$resolvedValue = $value;
+
return;
+
}
+
+
$fiber->resume($value);
+
},
+
function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber, $lowLevelFiber): void {
+
if ($lowLevelFiber !== null) {
+
FiberMap::unsetPromise($lowLevelFiber);
+
}
+
+
if (!$throwable instanceof \Throwable) {
+
$throwable = new \UnexpectedValueException(
+
'Promise rejected with unexpected value of type ' . (is_object($throwable) ? get_class($throwable) : gettype($throwable)) /** @phpstan-ignore-line */
+
);
+
+
// avoid garbage references by replacing all closures in call stack.
+
// what a lovely piece of code!
+
$r = new \ReflectionProperty('Exception', 'trace');
+
$trace = $r->getValue($throwable);
+
assert(\is_array($trace));
+
+
// Exception trace arguments only available when zend.exception_ignore_args is not set
+
// @codeCoverageIgnoreStart
+
foreach ($trace as $ti => $one) {
+
if (isset($one['args'])) {
+
foreach ($one['args'] as $ai => $arg) {
+
if ($arg instanceof \Closure) {
+
$trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
+
}
+
}
+
}
+
}
+
// @codeCoverageIgnoreEnd
+
$r->setValue($throwable, $trace);
+
}
+
+
if ($fiber === null) {
+
$rejected = true;
+
$rejectedThrowable = $throwable;
+
return;
+
}
+
+
$fiber->throw($throwable);
+
}
+
);
+
+
if ($resolved) {
+
return $resolvedValue;
+
}
+
+
if ($rejected) {
+
assert($rejectedThrowable instanceof \Throwable);
+
throw $rejectedThrowable;
+
}
+
+
if ($lowLevelFiber !== null) {
+
FiberMap::setPromise($lowLevelFiber, $promise);
+
}
+
+
$fiber = FiberFactory::create();
+
+
return $fiber->suspend();
+
}
+
+
/**
+
* Delay program execution for duration given in `$seconds`.
+
*
+
* ```php
+
* React\Async\delay($seconds);
+
* ```
+
*
+
* This function will only return after the given number of `$seconds` have
+
* elapsed. If there are no other events attached to this loop, it will behave
+
* similar to PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php).
+
*
+
* ```php
+
* echo 'a';
+
* React\Async\delay(1.0);
+
* echo 'b';
+
*
+
* // prints "a" at t=0.0s
+
* // prints "b" at t=1.0s
+
* ```
+
*
+
* Unlike PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php),
+
* this function may not necessarily halt execution of the entire process thread.
+
* Instead, it allows the event loop to run any other events attached to the
+
* same loop until the delay returns:
+
*
+
* ```php
+
* echo 'a';
+
* Loop::addTimer(1.0, function (): void {
+
* echo 'b';
+
* });
+
* React\Async\delay(3.0);
+
* echo 'c';
+
*
+
* // prints "a" at t=0.0s
+
* // prints "b" at t=1.0s
+
* // prints "c" at t=3.0s
+
* ```
+
*
+
* This behavior is especially useful if you want to delay the program execution
+
* of a particular routine, such as when building a simple polling or retry
+
* mechanism:
+
*
+
* ```php
+
* try {
+
* something();
+
* } catch (Throwable) {
+
* // in case of error, retry after a short delay
+
* React\Async\delay(1.0);
+
* something();
+
* }
+
* ```
+
*
+
* Because this function only returns after some time has passed, it can be
+
* considered *blocking* from the perspective of the calling code. You can avoid
+
* this blocking behavior by wrapping it in an [`async()` function](#async) call.
+
* Everything inside this function will still be blocked, but everything outside
+
* this function can be executed asynchronously without blocking:
+
*
+
* ```php
+
* Loop::addTimer(0.5, React\Async\async(function (): void {
+
* echo 'a';
+
* React\Async\delay(1.0);
+
* echo 'c';
+
* }));
+
*
+
* Loop::addTimer(1.0, function (): void {
+
* echo 'b';
+
* });
+
*
+
* // prints "a" at t=0.5s
+
* // prints "b" at t=1.0s
+
* // prints "c" at t=1.5s
+
* ```
+
*
+
* See also the [`async()` function](#async) for more details.
+
*
+
* Internally, the `$seconds` argument will be used as a timer for the loop so that
+
* it keeps running until this timer triggers. This implies that if you pass a
+
* really small (or negative) value, it will still start a timer and will thus
+
* trigger at the earliest possible time in the future.
+
*
+
* The function is implemented in such a way that it can be cancelled when it is
+
* running inside an [`async()` function](#async). Cancelling the resulting
+
* promise will clean up any pending timers and throw a `RuntimeException` from
+
* the pending delay which in turn would reject the resulting promise.
+
*
+
* ```php
+
* $promise = async(function (): void {
+
* echo 'a';
+
* delay(3.0);
+
* echo 'b';
+
* })();
+
*
+
* Loop::addTimer(2.0, function () use ($promise): void {
+
* $promise->cancel();
+
* });
+
*
+
* // prints "a" at t=0.0s
+
* // rejects $promise at t=2.0
+
* // never prints "b"
+
* ```
+
*
+
* @return void
+
* @throws \RuntimeException when the function is cancelled inside an `async()` function
+
* @see async()
+
* @uses await()
+
*/
+
function delay(float $seconds): void
+
{
+
/** @var ?TimerInterface $timer */
+
$timer = null;
+
+
await(new Promise(function (callable $resolve) use ($seconds, &$timer): void {
+
$timer = Loop::addTimer($seconds, fn() => $resolve(null));
+
}, function () use (&$timer): void {
+
assert($timer instanceof TimerInterface);
+
Loop::cancelTimer($timer);
+
throw new \RuntimeException('Delay cancelled');
+
}));
+
}
+
+
/**
+
* Execute a Generator-based coroutine to "await" promises.
+
*
+
* ```php
+
* React\Async\coroutine(function () {
+
* $browser = new React\Http\Browser();
+
*
+
* try {
+
* $response = yield $browser->get('https://example.com/');
+
* assert($response instanceof Psr\Http\Message\ResponseInterface);
+
* echo $response->getBody();
+
* } catch (Exception $e) {
+
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
* }
+
* });
+
* ```
+
*
+
* Using Generator-based coroutines is an alternative to directly using the
+
* underlying promise APIs. For many use cases, this makes using promise-based
+
* APIs much simpler, as it resembles a synchronous code flow more closely.
+
* The above example performs the equivalent of directly using the promise APIs:
+
*
+
* ```php
+
* $browser = new React\Http\Browser();
+
*
+
* $browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
+
* echo $response->getBody();
+
* }, function (Exception $e) {
+
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
* });
+
* ```
+
*
+
* The `yield` keyword can be used to "await" a promise resolution. Internally,
+
* it will turn the entire given `$function` into a [`Generator`](https://www.php.net/manual/en/class.generator.php).
+
* This allows the execution to be interrupted and resumed at the same place
+
* when the promise is fulfilled. The `yield` statement returns whatever the
+
* promise is fulfilled with. If the promise is rejected, it will throw an
+
* `Exception` or `Throwable`.
+
*
+
* The `coroutine()` function will always return a Promise which will be
+
* fulfilled with whatever your `$function` returns. Likewise, it will return
+
* a promise that will be rejected if you throw an `Exception` or `Throwable`
+
* from your `$function`. This allows you to easily create Promise-based
+
* functions:
+
*
+
* ```php
+
* $promise = React\Async\coroutine(function () {
+
* $browser = new React\Http\Browser();
+
* $urls = [
+
* 'https://example.com/alice',
+
* 'https://example.com/bob'
+
* ];
+
*
+
* $bytes = 0;
+
* foreach ($urls as $url) {
+
* $response = yield $browser->get($url);
+
* assert($response instanceof Psr\Http\Message\ResponseInterface);
+
* $bytes += $response->getBody()->getSize();
+
* }
+
* return $bytes;
+
* });
+
*
+
* $promise->then(function (int $bytes) {
+
* echo 'Total size: ' . $bytes . PHP_EOL;
+
* }, function (Exception $e) {
+
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
* });
+
* ```
+
*
+
* The previous example uses a `yield` statement inside a loop to highlight how
+
* this vastly simplifies consuming asynchronous operations. At the same time,
+
* this naive example does not leverage concurrent execution, as it will
+
* essentially "await" between each operation. In order to take advantage of
+
* concurrent execution within the given `$function`, you can "await" multiple
+
* promises by using a single `yield` together with Promise-based primitives
+
* like this:
+
*
+
* ```php
+
* $promise = React\Async\coroutine(function () {
+
* $browser = new React\Http\Browser();
+
* $urls = [
+
* 'https://example.com/alice',
+
* 'https://example.com/bob'
+
* ];
+
*
+
* $promises = [];
+
* foreach ($urls as $url) {
+
* $promises[] = $browser->get($url);
+
* }
+
*
+
* try {
+
* $responses = yield React\Promise\all($promises);
+
* } catch (Exception $e) {
+
* foreach ($promises as $promise) {
+
* $promise->cancel();
+
* }
+
* throw $e;
+
* }
+
*
+
* $bytes = 0;
+
* foreach ($responses as $response) {
+
* assert($response instanceof Psr\Http\Message\ResponseInterface);
+
* $bytes += $response->getBody()->getSize();
+
* }
+
* return $bytes;
+
* });
+
*
+
* $promise->then(function (int $bytes) {
+
* echo 'Total size: ' . $bytes . PHP_EOL;
+
* }, function (Exception $e) {
+
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
* });
+
* ```
+
*
+
* @template T
+
* @template TYield
+
* @template A1 (any number of function arguments, see https://github.com/phpstan/phpstan/issues/8214)
+
* @template A2
+
* @template A3
+
* @template A4
+
* @template A5
+
* @param callable(A1, A2, A3, A4, A5):(\Generator<mixed, PromiseInterface<TYield>, TYield, PromiseInterface<T>|T>|PromiseInterface<T>|T) $function
+
* @param mixed ...$args Optional list of additional arguments that will be passed to the given `$function` as is
+
* @return PromiseInterface<T>
+
* @since 3.0.0
+
*/
+
function coroutine(callable $function, mixed ...$args): PromiseInterface
+
{
+
try {
+
$generator = $function(...$args);
+
} catch (\Throwable $e) {
+
return reject($e);
+
}
+
+
if (!$generator instanceof \Generator) {
+
return resolve($generator);
+
}
+
+
$promise = null;
+
/** @var Deferred<T> $deferred*/
+
$deferred = new Deferred(function () use (&$promise) {
+
/** @var ?PromiseInterface<T> $promise */
+
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
+
$promise->cancel();
+
}
+
$promise = null;
+
});
+
+
/** @var callable $next */
+
$next = function () use ($deferred, $generator, &$next, &$promise) {
+
try {
+
if (!$generator->valid()) {
+
$next = null;
+
$deferred->resolve($generator->getReturn());
+
return;
+
}
+
} catch (\Throwable $e) {
+
$next = null;
+
$deferred->reject($e);
+
return;
+
}
+
+
$promise = $generator->current();
+
if (!$promise instanceof PromiseInterface) {
+
$next = null;
+
$deferred->reject(new \UnexpectedValueException(
+
'Expected coroutine to yield ' . PromiseInterface::class . ', but got ' . (is_object($promise) ? get_class($promise) : gettype($promise))
+
));
+
return;
+
}
+
+
/** @var PromiseInterface<TYield> $promise */
+
assert($next instanceof \Closure);
+
$promise->then(function ($value) use ($generator, $next) {
+
$generator->send($value);
+
$next();
+
}, function (\Throwable $reason) use ($generator, $next) {
+
$generator->throw($reason);
+
$next();
+
})->then(null, function (\Throwable $reason) use ($deferred, &$next) {
+
$next = null;
+
$deferred->reject($reason);
+
});
+
};
+
$next();
+
+
return $deferred->promise();
+
}
+
+
/**
+
* @template T
+
* @param iterable<callable():(PromiseInterface<T>|T)> $tasks
+
* @return PromiseInterface<array<T>>
+
*/
+
function parallel(iterable $tasks): PromiseInterface
+
{
+
/** @var array<int,PromiseInterface<T>> $pending */
+
$pending = [];
+
/** @var Deferred<array<T>> $deferred */
+
$deferred = new Deferred(function () use (&$pending) {
+
foreach ($pending as $promise) {
+
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
+
$promise->cancel();
+
}
+
}
+
$pending = [];
+
});
+
$results = [];
+
$continue = true;
+
+
$taskErrback = function ($error) use (&$pending, $deferred, &$continue) {
+
$continue = false;
+
$deferred->reject($error);
+
+
foreach ($pending as $promise) {
+
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
+
$promise->cancel();
+
}
+
}
+
$pending = [];
+
};
+
+
foreach ($tasks as $i => $task) {
+
$taskCallback = function ($result) use (&$results, &$pending, &$continue, $i, $deferred) {
+
$results[$i] = $result;
+
unset($pending[$i]);
+
+
if (!$pending && !$continue) {
+
$deferred->resolve($results);
+
}
+
};
+
+
$promise = \call_user_func($task);
+
assert($promise instanceof PromiseInterface);
+
$pending[$i] = $promise;
+
+
$promise->then($taskCallback, $taskErrback);
+
+
if (!$continue) {
+
break;
+
}
+
}
+
+
$continue = false;
+
if (!$pending) {
+
$deferred->resolve($results);
+
}
+
+
/** @var PromiseInterface<array<T>> Remove once defining `Deferred()` above is supported by PHPStan, see https://github.com/phpstan/phpstan/issues/11032 */
+
return $deferred->promise();
+
}
+
+
/**
+
* @template T
+
* @param iterable<callable():(PromiseInterface<T>|T)> $tasks
+
* @return PromiseInterface<array<T>>
+
*/
+
function series(iterable $tasks): PromiseInterface
+
{
+
$pending = null;
+
/** @var Deferred<array<T>> $deferred */
+
$deferred = new Deferred(function () use (&$pending) {
+
/** @var ?PromiseInterface<T> $pending */
+
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
+
$pending->cancel();
+
}
+
$pending = null;
+
});
+
$results = [];
+
+
if ($tasks instanceof \IteratorAggregate) {
+
$tasks = $tasks->getIterator();
+
assert($tasks instanceof \Iterator);
+
}
+
+
$taskCallback = function ($result) use (&$results, &$next) {
+
$results[] = $result;
+
/** @var \Closure $next */
+
$next();
+
};
+
+
$next = function () use (&$tasks, $taskCallback, $deferred, &$results, &$pending) {
+
if ($tasks instanceof \Iterator ? !$tasks->valid() : !$tasks) {
+
$deferred->resolve($results);
+
return;
+
}
+
+
if ($tasks instanceof \Iterator) {
+
$task = $tasks->current();
+
$tasks->next();
+
} else {
+
assert(\is_array($tasks));
+
$task = \array_shift($tasks);
+
}
+
+
assert(\is_callable($task));
+
$promise = \call_user_func($task);
+
assert($promise instanceof PromiseInterface);
+
$pending = $promise;
+
+
$promise->then($taskCallback, array($deferred, 'reject'));
+
};
+
+
$next();
+
+
/** @var PromiseInterface<array<T>> Remove once defining `Deferred()` above is supported by PHPStan, see https://github.com/phpstan/phpstan/issues/11032 */
+
return $deferred->promise();
+
}
+
+
/**
+
* @template T
+
* @param iterable<(callable():(PromiseInterface<T>|T))|(callable(mixed):(PromiseInterface<T>|T))> $tasks
+
* @return PromiseInterface<($tasks is non-empty-array|\Traversable ? T : null)>
+
*/
+
function waterfall(iterable $tasks): PromiseInterface
+
{
+
$pending = null;
+
/** @var Deferred<T> $deferred*/
+
$deferred = new Deferred(function () use (&$pending) {
+
/** @var ?PromiseInterface<T> $pending */
+
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
+
$pending->cancel();
+
}
+
$pending = null;
+
});
+
+
if ($tasks instanceof \IteratorAggregate) {
+
$tasks = $tasks->getIterator();
+
assert($tasks instanceof \Iterator);
+
}
+
+
/** @var callable $next */
+
$next = function ($value = null) use (&$tasks, &$next, $deferred, &$pending) {
+
if ($tasks instanceof \Iterator ? !$tasks->valid() : !$tasks) {
+
$deferred->resolve($value);
+
return;
+
}
+
+
if ($tasks instanceof \Iterator) {
+
$task = $tasks->current();
+
$tasks->next();
+
} else {
+
assert(\is_array($tasks));
+
$task = \array_shift($tasks);
+
}
+
+
assert(\is_callable($task));
+
$promise = \call_user_func_array($task, func_get_args());
+
assert($promise instanceof PromiseInterface);
+
$pending = $promise;
+
+
$promise->then($next, array($deferred, 'reject'));
+
};
+
+
$next();
+
+
return $deferred->promise();
+
}
+9
vendor/react/async/src/functions_include.php
···
+
<?php
+
+
namespace React\Async;
+
+
// @codeCoverageIgnoreStart
+
if (!\function_exists(__NAMESPACE__ . '\\parallel')) {
+
require __DIR__ . '/functions.php';
+
}
+
// @codeCoverageIgnoreEnd
+468
vendor/react/event-loop/CHANGELOG.md
···
+
# Changelog
+
+
## 1.5.0 (2023-11-13)
+
+
* Feature: Improve performance by using `spl_object_id()` on PHP 7.2+.
+
(#267 by @samsonasik)
+
+
* Feature: Full PHP 8.3 compatibility.
+
(#269 by @clue)
+
+
* Update tests for `ext-uv` on PHP 8+ and legacy PHP.
+
(#270 by @clue and #268 by @SimonFrings)
+
+
## 1.4.0 (2023-05-05)
+
+
* Feature: Improve performance of `Loop` by avoiding unneeded method calls.
+
(#266 by @clue)
+
+
* Feature: Support checking `EINTR` constant from `ext-pcntl` without `ext-sockets`.
+
(#265 by @clue)
+
+
* Minor documentation improvements.
+
(#254 by @nhedger)
+
+
* Improve test suite, run tests on PHP 8.2 and report failed assertions.
+
(#258 by @WyriHaximus, #264 by @clue and #251, #261 and #262 by @SimonFrings)
+
+
## 1.3.0 (2022-03-17)
+
+
* Feature: Improve default `StreamSelectLoop` to report any warnings for invalid streams.
+
(#245 by @clue)
+
+
* Feature: Improve performance of `StreamSelectLoop` when no timers are scheduled.
+
(#246 by @clue)
+
+
* Fix: Fix periodic timer with zero interval for `ExtEvLoop` and legacy `ExtLibevLoop`.
+
(#243 by @lucasnetau)
+
+
* Minor documentation improvements, update PHP version references.
+
(#240, #248 and #250 by @SimonFrings, #241 by @dbu and #249 by @clue)
+
+
* Improve test suite and test against PHP 8.1.
+
(#238 by @WyriHaximus and #242 by @clue)
+
+
## 1.2.0 (2021-07-11)
+
+
A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
+
+
* Feature: Introduce new concept of default loop with the new `Loop` class.
+
(#226 by @WyriHaximus, #229, #231 and #232 by @clue)
+
+
The `Loop` class exists as a convenient global accessor for the event loop.
+
It provides all methods that exist on the `LoopInterface` as static methods and
+
will automatically execute the loop at the end of the program:
+
+
```php
+
$timer = Loop::addPeriodicTimer(0.1, function () {
+
echo 'Tick' . PHP_EOL;
+
});
+
+
Loop::addTimer(1.0, function () use ($timer) {
+
Loop::cancelTimer($timer);
+
echo 'Done' . PHP_EOL;
+
});
+
```
+
+
The explicit loop instructions are still valid and may still be useful in some applications,
+
especially for a transition period towards the more concise style.
+
The `Loop::get()` method can be used to get the currently active event loop instance.
+
+
```php
+
// deprecated
+
$loop = React\EventLoop\Factory::create();
+
+
// new
+
$loop = React\EventLoop\Loop::get();
+
```
+
+
* Minor documentation improvements and mark legacy extensions as deprecated.
+
(#234 by @SimonFrings, #214 by @WyriHaximus and #233 and #235 by @nhedger)
+
+
* Improve test suite, use GitHub actions for continuous integration (CI),
+
update PHPUnit config and run tests on PHP 8.
+
(#212 and #215 by @SimonFrings and #230 by @clue)
+
+
## 1.1.1 (2020-01-01)
+
+
* Fix: Fix reporting connection refused errors with `ExtUvLoop` on Linux and `StreamSelectLoop` on Windows.
+
(#207 and #208 by @clue)
+
+
* Fix: Fix unsupported EventConfig and `SEGFAULT` on shutdown with `ExtEventLoop` on Windows.
+
(#205 by @clue)
+
+
* Fix: Prevent interval overflow for timers very far in the future with `ExtUvLoop`.
+
(#196 by @PabloKowalczyk)
+
+
* Fix: Check PCNTL functions for signal support instead of PCNTL extension with `StreamSelectLoop`.
+
(#195 by @clue)
+
+
* Add `.gitattributes` to exclude dev files from exports.
+
(#201 by @reedy)
+
+
* Improve test suite to fix testing `ExtUvLoop` on Travis,
+
fix Travis CI builds, do not install `libuv` on legacy PHP setups,
+
fix failing test cases due to inaccurate timers,
+
run tests on Windows via Travis CI and
+
run tests on PHP 7.4 and simplify test matrix and test setup.
+
(#197 by @WyriHaximus and #202, #203, #204 and #209 by @clue)
+
+
## 1.1.0 (2019-02-07)
+
+
* New UV based event loop (ext-uv).
+
(#112 by @WyriHaximus)
+
+
* Use high resolution timer on PHP 7.3+.
+
(#182 by @clue)
+
+
* Improve PCNTL signals by using async signal dispatching if available.
+
(#179 by @CharlotteDunois)
+
+
* Improve test suite and test suite set up.
+
(#174 by @WyriHaximus, #181 by @clue)
+
+
* Fix PCNTL signals edge case.
+
(#183 by @clue)
+
+
## 1.0.0 (2018-07-11)
+
+
* First stable LTS release, now following [SemVer](https://semver.org/).
+
We'd like to emphasize that this component is production ready and battle-tested.
+
We plan to support all long-term support (LTS) releases for at least 24 months,
+
so you have a rock-solid foundation to build on top of.
+
+
> Contains no other changes, so it's actually fully compatible with the v0.5.3 release.
+
+
## 0.5.3 (2018-07-09)
+
+
* Improve performance by importing global functions.
+
(#167 by @Ocramius)
+
+
* Improve test suite by simplifying test bootstrap by using dev autoloader.
+
(#169 by @lcobucci)
+
+
* Minor internal changes to improved backward compatibility with PHP 5.3.
+
(#166 by @Donatello-za)
+
+
## 0.5.2 (2018-04-24)
+
+
* Feature: Improve memory consumption and runtime performance for `StreamSelectLoop` timers.
+
(#164 by @clue)
+
+
* Improve test suite by removing I/O dependency at `StreamSelectLoopTest` to fix Mac OS X tests.
+
(#161 by @nawarian)
+
+
## 0.5.1 (2018-04-09)
+
+
* Feature: New `ExtEvLoop` (PECL ext-ev) (#148 by @kaduev13)
+
+
## 0.5.0 (2018-04-05)
+
+
A major feature release with a significant documentation overhaul and long overdue API cleanup!
+
+
This update involves a number of BC breaks due to dropped support for deprecated
+
functionality. We've tried hard to avoid BC breaks where possible and minimize
+
impact otherwise. We expect that most consumers of this package will actually
+
not be affected by any BC breaks, see below for more details.
+
+
We realize that the changes listed below may seem overwhelming, but we've tried
+
to be very clear about any possible BC breaks. Don't worry: In fact, all ReactPHP
+
components are already compatible and support both this new release as well as
+
providing backwards compatibility with the last release.
+
+
* Feature / BC break: Add support for signal handling via new
+
`LoopInterface::addSignal()` and `LoopInterface::removeSignal()` methods.
+
(#104 by @WyriHaximus and #111 and #150 by @clue)
+
+
```php
+
$loop->addSignal(SIGINT, function () {
+
echo 'CTRL-C';
+
});
+
```
+
+
* Feature: Significant documentation updates for `LoopInterface` and `Factory`.
+
(#100, #119, #126, #127, #159 and #160 by @clue, #113 by @WyriHaximus and #81 and #91 by @jsor)
+
+
* Feature: Add examples to ease getting started
+
(#99, #100 and #125 by @clue, #59 by @WyriHaximus and #143 by @jsor)
+
+
* Feature: Documentation for advanced timer concepts, such as monotonic time source vs wall-clock time
+
and high precision timers with millisecond accuracy or below.
+
(#130 and #157 by @clue)
+
+
* Feature: Documentation for advanced stream concepts, such as edge-triggered event listeners
+
and stream buffers and allow throwing Exception if stream resource is not supported.
+
(#129 and #158 by @clue)
+
+
* Feature: Throw `BadMethodCallException` on manual loop creation when required extension isn't installed.
+
(#153 by @WyriHaximus)
+
+
* Feature / BC break: First class support for legacy PHP 5.3 through PHP 7.2 and HHVM
+
and remove all `callable` type hints for consistency reasons.
+
(#141 and #151 by @clue)
+
+
* BC break: Documentation for timer API and clean up unneeded timer API.
+
(#102 by @clue)
+
+
Remove `TimerInterface::cancel()`, use `LoopInterface::cancelTimer()` instead:
+
+
```php
+
// old (method invoked on timer instance)
+
$timer->cancel();
+
+
// already supported before: invoke method on loop instance
+
$loop->cancelTimer($timer);
+
```
+
+
Remove unneeded `TimerInterface::setData()` and `TimerInterface::getData()`,
+
use closure binding to add arbitrary data to timer instead:
+
+
```php
+
// old (limited setData() and getData() only allows single variable)
+
$name = 'Tester';
+
$timer = $loop->addTimer(1.0, function ($timer) {
+
echo 'Hello ' . $timer->getData() . PHP_EOL;
+
});
+
$timer->setData($name);
+
+
// already supported before: closure binding allows any number of variables
+
$name = 'Tester';
+
$loop->addTimer(1.0, function () use ($name) {
+
echo 'Hello ' . $name . PHP_EOL;
+
});
+
```
+
+
Remove unneeded `TimerInterface::getLoop()`, use closure binding instead:
+
+
```php
+
// old (getLoop() called on timer instance)
+
$loop->addTimer(0.1, function ($timer) {
+
$timer->getLoop()->stop();
+
});
+
+
// already supported before: use closure binding as usual
+
$loop->addTimer(0.1, function () use ($loop) {
+
$loop->stop();
+
});
+
```
+
+
* BC break: Remove unneeded `LoopInterface::isTimerActive()` and
+
`TimerInterface::isActive()` to reduce API surface.
+
(#133 by @clue)
+
+
```php
+
// old (method on timer instance or on loop instance)
+
$timer->isActive();
+
$loop->isTimerActive($timer);
+
```
+
+
* BC break: Move `TimerInterface` one level up to `React\EventLoop\TimerInterface`.
+
(#138 by @WyriHaximus)
+
+
```php
+
// old (notice obsolete "Timer" namespace)
+
assert($timer instanceof React\EventLoop\Timer\TimerInterface);
+
+
// new
+
assert($timer instanceof React\EventLoop\TimerInterface);
+
```
+
+
* BC break: Remove unneeded `LoopInterface::nextTick()` (and internal `NextTickQueue`),
+
use `LoopInterface::futureTick()` instead.
+
(#30 by @clue)
+
+
```php
+
// old (removed)
+
$loop->nextTick(function () {
+
echo 'tick';
+
});
+
+
// already supported before
+
$loop->futureTick(function () {
+
echo 'tick';
+
});
+
```
+
+
* BC break: Remove unneeded `$loop` argument for `LoopInterface::futureTick()`
+
(and fix internal cyclic dependency).
+
(#103 by @clue)
+
+
```php
+
// old ($loop gets passed by default)
+
$loop->futureTick(function ($loop) {
+
$loop->stop();
+
});
+
+
// already supported before: use closure binding as usual
+
$loop->futureTick(function () use ($loop) {
+
$loop->stop();
+
});
+
```
+
+
* BC break: Remove unneeded `LoopInterface::tick()`.
+
(#72 by @jsor)
+
+
```php
+
// old (removed)
+
$loop->tick();
+
+
// suggested work around for testing purposes only
+
$loop->futureTick(function () use ($loop) {
+
$loop->stop();
+
});
+
```
+
+
* BC break: Documentation for advanced stream API and clean up unneeded stream API.
+
(#110 by @clue)
+
+
Remove unneeded `$loop` argument for `LoopInterface::addReadStream()`
+
and `LoopInterface::addWriteStream()`, use closure binding instead:
+
+
```php
+
// old ($loop gets passed by default)
+
$loop->addReadStream($stream, function ($stream, $loop) {
+
$loop->removeReadStream($stream);
+
});
+
+
// already supported before: use closure binding as usual
+
$loop->addReadStream($stream, function ($stream) use ($loop) {
+
$loop->removeReadStream($stream);
+
});
+
```
+
+
* BC break: Remove unneeded `LoopInterface::removeStream()` method,
+
use `LoopInterface::removeReadStream()` and `LoopInterface::removeWriteStream()` instead.
+
(#118 by @clue)
+
+
```php
+
// old
+
$loop->removeStream($stream);
+
+
// already supported before
+
$loop->removeReadStream($stream);
+
$loop->removeWriteStream($stream);
+
```
+
+
* BC break: Rename `LibEventLoop` to `ExtLibeventLoop` and `LibEvLoop` to `ExtLibevLoop`
+
for consistent naming for event loop implementations.
+
(#128 by @clue)
+
+
* BC break: Remove optional `EventBaseConfig` argument from `ExtEventLoop`
+
and make its `FEATURE_FDS` enabled by default.
+
(#156 by @WyriHaximus)
+
+
* BC break: Mark all classes as final to discourage inheritance.
+
(#131 by @clue)
+
+
* Fix: Fix `ExtEventLoop` to keep track of stream resources (refcount)
+
(#123 by @clue)
+
+
* Fix: Ensure large timer interval does not overflow on 32bit systems
+
(#132 by @clue)
+
+
* Fix: Fix separately removing readable and writable side of stream when closing
+
(#139 by @clue)
+
+
* Fix: Properly clean up event watchers for `ext-event` and `ext-libev`
+
(#149 by @clue)
+
+
* Fix: Minor code cleanup and remove unneeded references
+
(#145 by @seregazhuk)
+
+
* Fix: Discourage outdated `ext-libevent` on PHP 7
+
(#62 by @cboden)
+
+
* Improve test suite by adding forward compatibility with PHPUnit 6 and PHPUnit 5,
+
lock Travis distro so new defaults will not break the build,
+
improve test suite to be less fragile and increase test timeouts,
+
test against PHP 7.2 and reduce fwrite() call length to one chunk.
+
(#106 and #144 by @clue, #120 and #124 by @carusogabriel, #147 by nawarian and #92 by @kelunik)
+
+
* A number of changes were originally planned for this release but have been backported
+
to the last `v0.4.3` already: #74, #76, #79, #81 (refs #65, #66, #67), #88 and #93
+
+
## 0.4.3 (2017-04-27)
+
+
* Bug fix: Bugfix in the usage sample code #57 (@dandelionred)
+
* Improvement: Remove branch-alias definition #53 (@WyriHaximus)
+
* Improvement: StreamSelectLoop: Use fresh time so Timers added during stream events are accurate #51 (@andrewminerd)
+
* Improvement: Avoid deprecation warnings in test suite due to deprecation of getMock() in PHPUnit #68 (@martinschroeder)
+
* Improvement: Add PHPUnit 4.8 to require-dev #69 (@shaunbramley)
+
* Improvement: Increase test timeouts for HHVM and unify timeout handling #70 (@clue)
+
* Improvement: Travis improvements (backported from #74) #75 (@clue)
+
* Improvement: Test suite now uses socket pairs instead of memory streams #66 (@martinschroeder)
+
* Improvement: StreamSelectLoop: Test suite uses signal constant names in data provider #67 (@martinschroeder)
+
* Improvement: ExtEventLoop: No longer suppress all errors #65 (@mamciek)
+
* Improvement: Readme cleanup #89 (@jsor)
+
* Improvement: Restructure and improve README #90 (@jsor)
+
* Bug fix: StreamSelectLoop: Fix erroneous zero-time sleep (backport to 0.4) #94 (@jsor)
+
+
## 0.4.2 (2016-03-07)
+
+
* Bug fix: No longer error when signals sent to StreamSelectLoop
+
* Support HHVM and PHP7 (@ondrejmirtes, @cebe)
+
* Feature: Added support for EventConfig for ExtEventLoop (@steverhoades)
+
* Bug fix: Fixed an issue loading loop extension libs via autoloader (@czarpino)
+
+
## 0.4.1 (2014-04-13)
+
+
* Bug fix: null timeout in StreamSelectLoop causing 100% CPU usage (@clue)
+
* Bug fix: v0.3.4 changes merged for v0.4.1
+
+
## 0.4.0 (2014-02-02)
+
+
* Feature: Added `EventLoopInterface::nextTick()`, implemented in all event loops (@jmalloc)
+
* Feature: Added `EventLoopInterface::futureTick()`, implemented in all event loops (@jmalloc)
+
* Feature: Added `ExtEventLoop` implementation using pecl/event (@jmalloc)
+
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+
* BC break: New method: `EventLoopInterface::nextTick()`
+
* BC break: New method: `EventLoopInterface::futureTick()`
+
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+
## 0.3.5 (2016-12-28)
+
+
This is a compatibility release that eases upgrading to the v0.4 release branch.
+
You should consider upgrading to the v0.4 release branch.
+
+
* Feature: Cap min timer interval at 1µs, thus improving compatibility with v0.4
+
(#47 by @clue)
+
+
## 0.3.4 (2014-03-30)
+
+
* Bug fix: Changed StreamSelectLoop to use non-blocking behavior on tick() (@astephens25)
+
+
## 0.3.3 (2013-07-08)
+
+
* Bug fix: No error on removing non-existent streams (@clue)
+
* Bug fix: Do not silently remove feof listeners in `LibEvLoop`
+
+
## 0.3.0 (2013-04-14)
+
+
* BC break: New timers API (@nrk)
+
* BC break: Remove check on return value from stream callbacks (@nrk)
+
+
## 0.2.7 (2013-01-05)
+
+
* Bug fix: Fix libevent timers with PHP 5.3
+
* Bug fix: Fix libevent timer cancellation (@nrk)
+
+
## 0.2.6 (2012-12-26)
+
+
* Bug fix: Plug memory issue in libevent timers (@cameronjacobson)
+
* Bug fix: Correctly pause LibEvLoop on stop()
+
+
## 0.2.3 (2012-11-14)
+
+
* Feature: LibEvLoop, integration of `php-libev`
+
+
## 0.2.0 (2012-09-10)
+
+
* Version bump
+
+
## 0.1.1 (2012-07-12)
+
+
* Version bump
+
+
## 0.1.0 (2012-07-11)
+
+
* First tagged release
+21
vendor/react/event-loop/LICENSE
···
+
The MIT License (MIT)
+
+
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
+
+
Permission is hereby granted, free of charge, to any person obtaining a copy
+
of this software and associated documentation files (the "Software"), to deal
+
in the Software without restriction, including without limitation the rights
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+
copies of the Software, and to permit persons to whom the Software is furnished
+
to do so, subject to the following conditions:
+
+
The above copyright notice and this permission notice shall be included in all
+
copies or substantial portions of the Software.
+
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+
THE SOFTWARE.
+930
vendor/react/event-loop/README.md
···
+
# EventLoop
+
+
[![CI status](https://github.com/reactphp/event-loop/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/event-loop/actions)
+
[![installs on Packagist](https://img.shields.io/packagist/dt/react/event-loop?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/event-loop)
+
+
[ReactPHP](https://reactphp.org/)'s core reactor event loop that libraries can use for evented I/O.
+
+
In order for async based libraries to be interoperable, they need to use the
+
same event loop. This component provides a common `LoopInterface` that any
+
library can target. This allows them to be used in the same loop, with one
+
single [`run()`](#run) call that is controlled by the user.
+
+
**Table of contents**
+
+
* [Quickstart example](#quickstart-example)
+
* [Usage](#usage)
+
* [Loop](#loop)
+
* [Loop methods](#loop-methods)
+
* [Loop autorun](#loop-autorun)
+
* [get()](#get)
+
* [~~Factory~~](#factory)
+
* [~~create()~~](#create)
+
* [Loop implementations](#loop-implementations)
+
* [StreamSelectLoop](#streamselectloop)
+
* [ExtEventLoop](#exteventloop)
+
* [ExtEvLoop](#extevloop)
+
* [ExtUvLoop](#extuvloop)
+
* [~~ExtLibeventLoop~~](#extlibeventloop)
+
* [~~ExtLibevLoop~~](#extlibevloop)
+
* [LoopInterface](#loopinterface)
+
* [run()](#run)
+
* [stop()](#stop)
+
* [addTimer()](#addtimer)
+
* [addPeriodicTimer()](#addperiodictimer)
+
* [cancelTimer()](#canceltimer)
+
* [futureTick()](#futuretick)
+
* [addSignal()](#addsignal)
+
* [removeSignal()](#removesignal)
+
* [addReadStream()](#addreadstream)
+
* [addWriteStream()](#addwritestream)
+
* [removeReadStream()](#removereadstream)
+
* [removeWriteStream()](#removewritestream)
+
* [Install](#install)
+
* [Tests](#tests)
+
* [License](#license)
+
* [More](#more)
+
+
## Quickstart example
+
+
Here is an async HTTP server built with just the event loop.
+
+
```php
+
<?php
+
+
use React\EventLoop\Loop;
+
+
require __DIR__ . '/vendor/autoload.php';
+
+
$server = stream_socket_server('tcp://127.0.0.1:8080');
+
stream_set_blocking($server, false);
+
+
Loop::addReadStream($server, function ($server) {
+
$conn = stream_socket_accept($server);
+
$data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n";
+
Loop::addWriteStream($conn, function ($conn) use (&$data) {
+
$written = fwrite($conn, $data);
+
if ($written === strlen($data)) {
+
fclose($conn);
+
Loop::removeWriteStream($conn);
+
} else {
+
$data = substr($data, $written);
+
}
+
});
+
});
+
+
Loop::addPeriodicTimer(5, function () {
+
$memory = memory_get_usage() / 1024;
+
$formatted = number_format($memory, 3).'K';
+
echo "Current memory usage: {$formatted}\n";
+
});
+
```
+
+
See also the [examples](examples).
+
+
## Usage
+
+
Typical applications would use the [`Loop` class](#loop) to use the default
+
event loop like this:
+
+
```php
+
use React\EventLoop\Loop;
+
+
$timer = Loop::addPeriodicTimer(0.1, function () {
+
echo 'Tick' . PHP_EOL;
+
});
+
+
Loop::addTimer(1.0, function () use ($timer) {
+
Loop::cancelTimer($timer);
+
echo 'Done' . PHP_EOL;
+
});
+
```
+
+
As an alternative, you can also explicitly create an event loop instance at the
+
beginning, reuse it throughout your program and finally run it at the end of the
+
program like this:
+
+
```php
+
$loop = React\EventLoop\Loop::get(); // or deprecated React\EventLoop\Factory::create();
+
+
$timer = $loop->addPeriodicTimer(0.1, function () {
+
echo 'Tick' . PHP_EOL;
+
});
+
+
$loop->addTimer(1.0, function () use ($loop, $timer) {
+
$loop->cancelTimer($timer);
+
echo 'Done' . PHP_EOL;
+
});
+
+
$loop->run();
+
```
+
+
While the former is more concise, the latter is more explicit.
+
In both cases, the program would perform the exact same steps.
+
+
1. The event loop instance is created at the beginning of the program. This is
+
implicitly done the first time you call the [`Loop` class](#loop) or
+
explicitly when using the deprecated [`Factory::create()` method](#create)
+
(or manually instantiating any of the [loop implementations](#loop-implementations)).
+
2. The event loop is used directly or passed as an instance to library and
+
application code. In this example, a periodic timer is registered with the
+
event loop which simply outputs `Tick` every fraction of a second until another
+
timer stops the periodic timer after a second.
+
3. The event loop is run at the end of the program. This is automatically done
+
when using the [`Loop` class](#loop) or explicitly with a single [`run()`](#run)
+
call at the end of the program.
+
+
As of `v1.2.0`, we highly recommend using the [`Loop` class](#loop).
+
The explicit loop instructions are still valid and may still be useful in some
+
applications, especially for a transition period towards the more concise style.
+
+
### Loop
+
+
The `Loop` class exists as a convenient global accessor for the event loop.
+
+
#### Loop methods
+
+
The `Loop` class provides all methods that exist on the [`LoopInterface`](#loopinterface)
+
as static methods:
+
+
* [run()](#run)
+
* [stop()](#stop)
+
* [addTimer()](#addtimer)
+
* [addPeriodicTimer()](#addperiodictimer)
+
* [cancelTimer()](#canceltimer)
+
* [futureTick()](#futuretick)
+
* [addSignal()](#addsignal)
+
* [removeSignal()](#removesignal)
+
* [addReadStream()](#addreadstream)
+
* [addWriteStream()](#addwritestream)
+
* [removeReadStream()](#removereadstream)
+
* [removeWriteStream()](#removewritestream)
+
+
If you're working with the event loop in your application code, it's often
+
easiest to directly interface with the static methods defined on the `Loop` class
+
like this:
+
+
```php
+
use React\EventLoop\Loop;
+
+
$timer = Loop::addPeriodicTimer(0.1, function () {
+
echo 'Tick' . PHP_EOL;
+
});
+
+
Loop::addTimer(1.0, function () use ($timer) {
+
Loop::cancelTimer($timer);
+
echo 'Done' . PHP_EOL;
+
});
+
```
+
+
On the other hand, if you're familiar with object-oriented programming (OOP) and
+
dependency injection (DI), you may want to inject an event loop instance and
+
invoke instance methods on the `LoopInterface` like this:
+
+
```php
+
use React\EventLoop\Loop;
+
use React\EventLoop\LoopInterface;
+
+
class Greeter
+
{
+
private $loop;
+
+
public function __construct(LoopInterface $loop)
+
{
+
$this->loop = $loop;
+
}
+
+
public function greet(string $name)
+
{
+
$this->loop->addTimer(1.0, function () use ($name) {
+
echo 'Hello ' . $name . '!' . PHP_EOL;
+
});
+
}
+
}
+
+
$greeter = new Greeter(Loop::get());
+
$greeter->greet('Alice');
+
$greeter->greet('Bob');
+
```
+
+
Each static method call will be forwarded as-is to the underlying event loop
+
instance by using the [`Loop::get()`](#get) call internally.
+
See [`LoopInterface`](#loopinterface) for more details about available methods.
+
+
#### Loop autorun
+
+
When using the `Loop` class, it will automatically execute the loop at the end of
+
the program. This means the following example will schedule a timer and will
+
automatically execute the program until the timer event fires:
+
+
```php
+
use React\EventLoop\Loop;
+
+
Loop::addTimer(1.0, function () {
+
echo 'Hello' . PHP_EOL;
+
});
+
```
+
+
As of `v1.2.0`, we highly recommend using the `Loop` class this way and omitting any
+
explicit [`run()`](#run) calls. For BC reasons, the explicit [`run()`](#run)
+
method is still valid and may still be useful in some applications, especially
+
for a transition period towards the more concise style.
+
+
If you don't want the `Loop` to run automatically, you can either explicitly
+
[`run()`](#run) or [`stop()`](#stop) it. This can be useful if you're using
+
a global exception handler like this:
+
+
```php
+
use React\EventLoop\Loop;
+
+
Loop::addTimer(10.0, function () {
+
echo 'Never happens';
+
});
+
+
set_exception_handler(function (Throwable $e) {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
Loop::stop();
+
});
+
+
throw new RuntimeException('Demo');
+
```
+
+
#### get()
+
+
The `get(): LoopInterface` method can be used to
+
get the currently active event loop instance.
+
+
This method will always return the same event loop instance throughout the
+
lifetime of your application.
+
+
```php
+
use React\EventLoop\Loop;
+
use React\EventLoop\LoopInterface;
+
+
$loop = Loop::get();
+
+
assert($loop instanceof LoopInterface);
+
assert($loop === Loop::get());
+
```
+
+
This is particularly useful if you're using object-oriented programming (OOP)
+
and dependency injection (DI). In this case, you may want to inject an event
+
loop instance and invoke instance methods on the `LoopInterface` like this:
+
+
```php
+
use React\EventLoop\Loop;
+
use React\EventLoop\LoopInterface;
+
+
class Greeter
+
{
+
private $loop;
+
+
public function __construct(LoopInterface $loop)
+
{
+
$this->loop = $loop;
+
}
+
+
public function greet(string $name)
+
{
+
$this->loop->addTimer(1.0, function () use ($name) {
+
echo 'Hello ' . $name . '!' . PHP_EOL;
+
});
+
}
+
}
+
+
$greeter = new Greeter(Loop::get());
+
$greeter->greet('Alice');
+
$greeter->greet('Bob');
+
```
+
+
See [`LoopInterface`](#loopinterface) for more details about available methods.
+
+
### ~~Factory~~
+
+
> Deprecated since v1.2.0, see [`Loop` class](#loop) instead.
+
+
The deprecated `Factory` class exists as a convenient way to pick the best available
+
[event loop implementation](#loop-implementations).
+
+
#### ~~create()~~
+
+
> Deprecated since v1.2.0, see [`Loop::get()`](#get) instead.
+
+
The deprecated `create(): LoopInterface` method can be used to
+
create a new event loop instance:
+
+
```php
+
// deprecated
+
$loop = React\EventLoop\Factory::create();
+
+
// new
+
$loop = React\EventLoop\Loop::get();
+
```
+
+
This method always returns an instance implementing [`LoopInterface`](#loopinterface),
+
the actual [event loop implementation](#loop-implementations) is an implementation detail.
+
+
This method should usually only be called once at the beginning of the program.
+
+
### Loop implementations
+
+
In addition to the [`LoopInterface`](#loopinterface), there are a number of
+
event loop implementations provided.
+
+
All of the event loops support these features:
+
+
* File descriptor polling
+
* One-off timers
+
* Periodic timers
+
* Deferred execution on future loop tick
+
+
For most consumers of this package, the underlying event loop implementation is
+
an implementation detail.
+
You should use the [`Loop` class](#loop) to automatically create a new instance.
+
+
Advanced! If you explicitly need a certain event loop implementation, you can
+
manually instantiate one of the following classes.
+
Note that you may have to install the required PHP extensions for the respective
+
event loop implementation first or they will throw a `BadMethodCallException` on creation.
+
+
#### StreamSelectLoop
+
+
A `stream_select()` based event loop.
+
+
This uses the [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
+
function and is the only implementation that works out of the box with PHP.
+
+
This event loop works out of the box on PHP 5.3 through PHP 8+ and HHVM.
+
This means that no installation is required and this library works on all
+
platforms and supported PHP versions.
+
Accordingly, the [`Loop` class](#loop) and the deprecated [`Factory`](#factory)
+
will use this event loop by default if you do not install any of the event loop
+
extensions listed below.
+
+
Under the hood, it does a simple `select` system call.
+
This system call is limited to the maximum file descriptor number of
+
`FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)`
+
(`m` being the maximum file descriptor number passed).
+
This means that you may run into issues when handling thousands of streams
+
concurrently and you may want to look into using one of the alternative
+
event loop implementations listed below in this case.
+
If your use case is among the many common use cases that involve handling only
+
dozens or a few hundred streams at once, then this event loop implementation
+
performs really well.
+
+
If you want to use signal handling (see also [`addSignal()`](#addsignal) below),
+
this event loop implementation requires `ext-pcntl`.
+
This extension is only available for Unix-like platforms and does not support
+
Windows.
+
It is commonly installed as part of many PHP distributions.
+
If this extension is missing (or you're running on Windows), signal handling is
+
not supported and throws a `BadMethodCallException` instead.
+
+
This event loop is known to rely on wall-clock time to schedule future timers
+
when using any version before PHP 7.3, because a monotonic time source is
+
only available as of PHP 7.3 (`hrtime()`).
+
While this does not affect many common use cases, this is an important
+
distinction for programs that rely on a high time precision or on systems
+
that are subject to discontinuous time adjustments (time jumps).
+
This means that if you schedule a timer to trigger in 30s on PHP < 7.3 and
+
then adjust your system time forward by 20s, the timer may trigger in 10s.
+
See also [`addTimer()`](#addtimer) for more details.
+
+
#### ExtEventLoop
+
+
An `ext-event` based event loop.
+
+
This uses the [`event` PECL extension](https://pecl.php.net/package/event),
+
that provides an interface to `libevent` library.
+
`libevent` itself supports a number of system-specific backends (epoll, kqueue).
+
+
This loop is known to work with PHP 5.4 through PHP 8+.
+
+
#### ExtEvLoop
+
+
An `ext-ev` based event loop.
+
+
This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev),
+
that provides an interface to `libev` library.
+
`libev` itself supports a number of system-specific backends (epoll, kqueue).
+
+
+
This loop is known to work with PHP 5.4 through PHP 8+.
+
+
#### ExtUvLoop
+
+
An `ext-uv` based event loop.
+
+
This loop uses the [`uv` PECL extension](https://pecl.php.net/package/uv),
+
that provides an interface to `libuv` library.
+
`libuv` itself supports a number of system-specific backends (epoll, kqueue).
+
+
This loop is known to work with PHP 7+.
+
+
#### ~~ExtLibeventLoop~~
+
+
> Deprecated since v1.2.0, use [`ExtEventLoop`](#exteventloop) instead.
+
+
An `ext-libevent` based event loop.
+
+
This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent),
+
that provides an interface to `libevent` library.
+
`libevent` itself supports a number of system-specific backends (epoll, kqueue).
+
+
This event loop does only work with PHP 5.
+
An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for
+
PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s.
+
To reiterate: Using this event loop on PHP 7 is not recommended.
+
Accordingly, neither the [`Loop` class](#loop) nor the deprecated
+
[`Factory` class](#factory) will try to use this event loop on PHP 7.
+
+
This event loop is known to trigger a readable listener only if
+
the stream *becomes* readable (edge-triggered) and may not trigger if the
+
stream has already been readable from the beginning.
+
This also implies that a stream may not be recognized as readable when data
+
is still left in PHP's internal stream buffers.
+
As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
+
to disable PHP's internal read buffer in this case.
+
See also [`addReadStream()`](#addreadstream) for more details.
+
+
#### ~~ExtLibevLoop~~
+
+
> Deprecated since v1.2.0, use [`ExtEvLoop`](#extevloop) instead.
+
+
An `ext-libev` based event loop.
+
+
This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev),
+
that provides an interface to `libev` library.
+
`libev` itself supports a number of system-specific backends (epoll, kqueue).
+
+
This loop does only work with PHP 5.
+
An update for PHP 7 is [unlikely](https://github.com/m4rw3r/php-libev/issues/8)
+
to happen any time soon.
+
+
### LoopInterface
+
+
#### run()
+
+
The `run(): void` method can be used to
+
run the event loop until there are no more tasks to perform.
+
+
For many applications, this method is the only directly visible
+
invocation on the event loop.
+
As a rule of thumb, it is usually recommended to attach everything to the
+
same loop instance and then run the loop once at the bottom end of the
+
application.
+
+
```php
+
$loop->run();
+
```
+
+
This method will keep the loop running until there are no more tasks
+
to perform. In other words: This method will block until the last
+
timer, stream and/or signal has been removed.
+
+
Likewise, it is imperative to ensure the application actually invokes
+
this method once. Adding listeners to the loop and missing to actually
+
run it will result in the application exiting without actually waiting
+
for any of the attached listeners.
+
+
This method MUST NOT be called while the loop is already running.
+
This method MAY be called more than once after it has explicitly been
+
[`stop()`ped](#stop) or after it automatically stopped because it
+
previously did no longer have anything to do.
+
+
#### stop()
+
+
The `stop(): void` method can be used to
+
instruct a running event loop to stop.
+
+
This method is considered advanced usage and should be used with care.
+
As a rule of thumb, it is usually recommended to let the loop stop
+
only automatically when it no longer has anything to do.
+
+
This method can be used to explicitly instruct the event loop to stop:
+
+
```php
+
$loop->addTimer(3.0, function () use ($loop) {
+
$loop->stop();
+
});
+
```
+
+
Calling this method on a loop instance that is not currently running or
+
on a loop instance that has already been stopped has no effect.
+
+
#### addTimer()
+
+
The `addTimer(float $interval, callable $callback): TimerInterface` method can be used to
+
enqueue a callback to be invoked once after the given interval.
+
+
The second parameter MUST be a timer callback function that accepts
+
the timer instance as its only parameter.
+
If you don't use the timer instance inside your timer callback function
+
you MAY use a function which has no parameters at all.
+
+
The timer callback function MUST NOT throw an `Exception`.
+
The return value of the timer callback function will be ignored and has
+
no effect, so for performance reasons you're recommended to not return
+
any excessive data structures.
+
+
This method returns a timer instance. The same timer instance will also be
+
passed into the timer callback function as described above.
+
You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer.
+
Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure
+
the callback will be invoked only once after the given interval.
+
+
```php
+
$loop->addTimer(0.8, function () {
+
echo 'world!' . PHP_EOL;
+
});
+
+
$loop->addTimer(0.3, function () {
+
echo 'hello ';
+
});
+
```
+
+
See also [example #1](examples).
+
+
If you want to access any variables within your callback function, you
+
can bind arbitrary data to a callback closure like this:
+
+
```php
+
function hello($name, LoopInterface $loop)
+
{
+
$loop->addTimer(1.0, function () use ($name) {
+
echo "hello $name\n";
+
});
+
}
+
+
hello('Tester', $loop);
+
```
+
+
This interface does not enforce any particular timer resolution, so
+
special care may have to be taken if you rely on very high precision with
+
millisecond accuracy or below. Event loop implementations SHOULD work on
+
a best effort basis and SHOULD provide at least millisecond accuracy
+
unless otherwise noted. Many existing event loop implementations are
+
known to provide microsecond accuracy, but it's generally not recommended
+
to rely on this high precision.
+
+
Similarly, the execution order of timers scheduled to execute at the
+
same time (within its possible accuracy) is not guaranteed.
+
+
This interface suggests that event loop implementations SHOULD use a
+
monotonic time source if available. Given that a monotonic time source is
+
only available as of PHP 7.3 by default, event loop implementations MAY
+
fall back to using wall-clock time.
+
While this does not affect many common use cases, this is an important
+
distinction for programs that rely on a high time precision or on systems
+
that are subject to discontinuous time adjustments (time jumps).
+
This means that if you schedule a timer to trigger in 30s and then adjust
+
your system time forward by 20s, the timer SHOULD still trigger in 30s.
+
See also [event loop implementations](#loop-implementations) for more details.
+
+
#### addPeriodicTimer()
+
+
The `addPeriodicTimer(float $interval, callable $callback): TimerInterface` method can be used to
+
enqueue a callback to be invoked repeatedly after the given interval.
+
+
The second parameter MUST be a timer callback function that accepts
+
the timer instance as its only parameter.
+
If you don't use the timer instance inside your timer callback function
+
you MAY use a function which has no parameters at all.
+
+
The timer callback function MUST NOT throw an `Exception`.
+
The return value of the timer callback function will be ignored and has
+
no effect, so for performance reasons you're recommended to not return
+
any excessive data structures.
+
+
This method returns a timer instance. The same timer instance will also be
+
passed into the timer callback function as described above.
+
Unlike [`addTimer()`](#addtimer), this method will ensure the callback
+
will be invoked infinitely after the given interval or until you invoke
+
[`cancelTimer`](#canceltimer).
+
+
```php
+
$timer = $loop->addPeriodicTimer(0.1, function () {
+
echo 'tick!' . PHP_EOL;
+
});
+
+
$loop->addTimer(1.0, function () use ($loop, $timer) {
+
$loop->cancelTimer($timer);
+
echo 'Done' . PHP_EOL;
+
});
+
```
+
+
See also [example #2](examples).
+
+
If you want to limit the number of executions, you can bind
+
arbitrary data to a callback closure like this:
+
+
```php
+
function hello($name, LoopInterface $loop)
+
{
+
$n = 3;
+
$loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) {
+
if ($n > 0) {
+
--$n;
+
echo "hello $name\n";
+
} else {
+
$loop->cancelTimer($timer);
+
}
+
});
+
}
+
+
hello('Tester', $loop);
+
```
+
+
This interface does not enforce any particular timer resolution, so
+
special care may have to be taken if you rely on very high precision with
+
millisecond accuracy or below. Event loop implementations SHOULD work on
+
a best effort basis and SHOULD provide at least millisecond accuracy
+
unless otherwise noted. Many existing event loop implementations are
+
known to provide microsecond accuracy, but it's generally not recommended
+
to rely on this high precision.
+
+
Similarly, the execution order of timers scheduled to execute at the
+
same time (within its possible accuracy) is not guaranteed.
+
+
This interface suggests that event loop implementations SHOULD use a
+
monotonic time source if available. Given that a monotonic time source is
+
only available as of PHP 7.3 by default, event loop implementations MAY
+
fall back to using wall-clock time.
+
While this does not affect many common use cases, this is an important
+
distinction for programs that rely on a high time precision or on systems
+
that are subject to discontinuous time adjustments (time jumps).
+
This means that if you schedule a timer to trigger in 30s and then adjust
+
your system time forward by 20s, the timer SHOULD still trigger in 30s.
+
See also [event loop implementations](#loop-implementations) for more details.
+
+
Additionally, periodic timers may be subject to timer drift due to
+
re-scheduling after each invocation. As such, it's generally not
+
recommended to rely on this for high precision intervals with millisecond
+
accuracy or below.
+
+
#### cancelTimer()
+
+
The `cancelTimer(TimerInterface $timer): void` method can be used to
+
cancel a pending timer.
+
+
See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples).
+
+
Calling this method on a timer instance that has not been added to this
+
loop instance or on a timer that has already been cancelled has no effect.
+
+
#### futureTick()
+
+
The `futureTick(callable $listener): void` method can be used to
+
schedule a callback to be invoked on a future tick of the event loop.
+
+
This works very much similar to timers with an interval of zero seconds,
+
but does not require the overhead of scheduling a timer queue.
+
+
The tick callback function MUST be able to accept zero parameters.
+
+
The tick callback function MUST NOT throw an `Exception`.
+
The return value of the tick callback function will be ignored and has
+
no effect, so for performance reasons you're recommended to not return
+
any excessive data structures.
+
+
If you want to access any variables within your callback function, you
+
can bind arbitrary data to a callback closure like this:
+
+
```php
+
function hello($name, LoopInterface $loop)
+
{
+
$loop->futureTick(function () use ($name) {
+
echo "hello $name\n";
+
});
+
}
+
+
hello('Tester', $loop);
+
```
+
+
Unlike timers, tick callbacks are guaranteed to be executed in the order
+
they are enqueued.
+
Also, once a callback is enqueued, there's no way to cancel this operation.
+
+
This is often used to break down bigger tasks into smaller steps (a form
+
of cooperative multitasking).
+
+
```php
+
$loop->futureTick(function () {
+
echo 'b';
+
});
+
$loop->futureTick(function () {
+
echo 'c';
+
});
+
echo 'a';
+
```
+
+
See also [example #3](examples).
+
+
#### addSignal()
+
+
The `addSignal(int $signal, callable $listener): void` method can be used to
+
register a listener to be notified when a signal has been caught by this process.
+
+
This is useful to catch user interrupt signals or shutdown signals from
+
tools like `supervisor` or `systemd`.
+
+
The second parameter MUST be a listener callback function that accepts
+
the signal as its only parameter.
+
If you don't use the signal inside your listener callback function
+
you MAY use a function which has no parameters at all.
+
+
The listener callback function MUST NOT throw an `Exception`.
+
The return value of the listener callback function will be ignored and has
+
no effect, so for performance reasons you're recommended to not return
+
any excessive data structures.
+
+
```php
+
$loop->addSignal(SIGINT, function (int $signal) {
+
echo 'Caught user interrupt signal' . PHP_EOL;
+
});
+
```
+
+
See also [example #4](examples).
+
+
Signaling is only available on Unix-like platforms, Windows isn't
+
supported due to operating system limitations.
+
This method may throw a `BadMethodCallException` if signals aren't
+
supported on this platform, for example when required extensions are
+
missing.
+
+
**Note: A listener can only be added once to the same signal, any
+
attempts to add it more than once will be ignored.**
+
+
#### removeSignal()
+
+
The `removeSignal(int $signal, callable $listener): void` method can be used to
+
remove a previously added signal listener.
+
+
```php
+
$loop->removeSignal(SIGINT, $listener);
+
```
+
+
Any attempts to remove listeners that aren't registered will be ignored.
+
+
#### addReadStream()
+
+
> Advanced! Note that this low-level API is considered advanced usage.
+
Most use cases should probably use the higher-level
+
[readable Stream API](https://github.com/reactphp/stream#readablestreaminterface)
+
instead.
+
+
The `addReadStream(resource $stream, callable $callback): void` method can be used to
+
register a listener to be notified when a stream is ready to read.
+
+
The first parameter MUST be a valid stream resource that supports
+
checking whether it is ready to read by this loop implementation.
+
A single stream resource MUST NOT be added more than once.
+
Instead, either call [`removeReadStream()`](#removereadstream) first or
+
react to this event with a single listener and then dispatch from this
+
listener. This method MAY throw an `Exception` if the given resource type
+
is not supported by this loop implementation.
+
+
The second parameter MUST be a listener callback function that accepts
+
the stream resource as its only parameter.
+
If you don't use the stream resource inside your listener callback function
+
you MAY use a function which has no parameters at all.
+
+
The listener callback function MUST NOT throw an `Exception`.
+
The return value of the listener callback function will be ignored and has
+
no effect, so for performance reasons you're recommended to not return
+
any excessive data structures.
+
+
If you want to access any variables within your callback function, you
+
can bind arbitrary data to a callback closure like this:
+
+
```php
+
$loop->addReadStream($stream, function ($stream) use ($name) {
+
echo $name . ' said: ' . fread($stream);
+
});
+
```
+
+
See also [example #11](examples).
+
+
You can invoke [`removeReadStream()`](#removereadstream) to remove the
+
read event listener for this stream.
+
+
The execution order of listeners when multiple streams become ready at
+
the same time is not guaranteed.
+
+
Some event loop implementations are known to only trigger the listener if
+
the stream *becomes* readable (edge-triggered) and may not trigger if the
+
stream has already been readable from the beginning.
+
This also implies that a stream may not be recognized as readable when data
+
is still left in PHP's internal stream buffers.
+
As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
+
to disable PHP's internal read buffer in this case.
+
+
#### addWriteStream()
+
+
> Advanced! Note that this low-level API is considered advanced usage.
+
Most use cases should probably use the higher-level
+
[writable Stream API](https://github.com/reactphp/stream#writablestreaminterface)
+
instead.
+
+
The `addWriteStream(resource $stream, callable $callback): void` method can be used to
+
register a listener to be notified when a stream is ready to write.
+
+
The first parameter MUST be a valid stream resource that supports
+
checking whether it is ready to write by this loop implementation.
+
A single stream resource MUST NOT be added more than once.
+
Instead, either call [`removeWriteStream()`](#removewritestream) first or
+
react to this event with a single listener and then dispatch from this
+
listener. This method MAY throw an `Exception` if the given resource type
+
is not supported by this loop implementation.
+
+
The second parameter MUST be a listener callback function that accepts
+
the stream resource as its only parameter.
+
If you don't use the stream resource inside your listener callback function
+
you MAY use a function which has no parameters at all.
+
+
The listener callback function MUST NOT throw an `Exception`.
+
The return value of the listener callback function will be ignored and has
+
no effect, so for performance reasons you're recommended to not return
+
any excessive data structures.
+
+
If you want to access any variables within your callback function, you
+
can bind arbitrary data to a callback closure like this:
+
+
```php
+
$loop->addWriteStream($stream, function ($stream) use ($name) {
+
fwrite($stream, 'Hello ' . $name);
+
});
+
```
+
+
See also [example #12](examples).
+
+
You can invoke [`removeWriteStream()`](#removewritestream) to remove the
+
write event listener for this stream.
+
+
The execution order of listeners when multiple streams become ready at
+
the same time is not guaranteed.
+
+
#### removeReadStream()
+
+
The `removeReadStream(resource $stream): void` method can be used to
+
remove the read event listener for the given stream.
+
+
Removing a stream from the loop that has already been removed or trying
+
to remove a stream that was never added or is invalid has no effect.
+
+
#### removeWriteStream()
+
+
The `removeWriteStream(resource $stream): void` method can be used to
+
remove the write event listener for the given stream.
+
+
Removing a stream from the loop that has already been removed or trying
+
to remove a stream that was never added or is invalid has no effect.
+
+
## Install
+
+
The recommended way to install this library is [through Composer](https://getcomposer.org/).
+
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+
This project follows [SemVer](https://semver.org/).
+
This will install the latest supported version:
+
+
```bash
+
composer require react/event-loop:^1.5
+
```
+
+
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+
This project aims to run on any platform and thus does not require any PHP
+
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
+
HHVM.
+
It's *highly recommended to use the latest supported PHP version* for this project.
+
+
Installing any of the event loop extensions is suggested, but entirely optional.
+
See also [event loop implementations](#loop-implementations) for more details.
+
+
## Tests
+
+
To run the test suite, you first need to clone this repo and then install all
+
dependencies [through Composer](https://getcomposer.org/):
+
+
```bash
+
composer install
+
```
+
+
To run the test suite, go to the project root and run:
+
+
```bash
+
vendor/bin/phpunit
+
```
+
+
## License
+
+
MIT, see [LICENSE file](LICENSE).
+
+
## More
+
+
* See our [Stream component](https://github.com/reactphp/stream) for more
+
information on how streams are used in real-world applications.
+
* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the
+
[dependents on Packagist](https://packagist.org/packages/react/event-loop/dependents)
+
for a list of packages that use the EventLoop in real-world applications.
+47
vendor/react/event-loop/composer.json
···
+
{
+
"name": "react/event-loop",
+
"description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
+
"keywords": ["event-loop", "asynchronous"],
+
"license": "MIT",
+
"authors": [
+
{
+
"name": "Christian Lück",
+
"homepage": "https://clue.engineering/",
+
"email": "christian@clue.engineering"
+
},
+
{
+
"name": "Cees-Jan Kiewiet",
+
"homepage": "https://wyrihaximus.net/",
+
"email": "reactphp@ceesjankiewiet.nl"
+
},
+
{
+
"name": "Jan Sorgalla",
+
"homepage": "https://sorgalla.com/",
+
"email": "jsorgalla@gmail.com"
+
},
+
{
+
"name": "Chris Boden",
+
"homepage": "https://cboden.dev/",
+
"email": "cboden@gmail.com"
+
}
+
],
+
"require": {
+
"php": ">=5.3.0"
+
},
+
"require-dev": {
+
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+
},
+
"suggest": {
+
"ext-pcntl": "For signal handling support when using the StreamSelectLoop"
+
},
+
"autoload": {
+
"psr-4": {
+
"React\\EventLoop\\": "src/"
+
}
+
},
+
"autoload-dev": {
+
"psr-4": {
+
"React\\Tests\\EventLoop\\": "tests/"
+
}
+
}
+
}
+253
vendor/react/event-loop/src/ExtEvLoop.php
···
+
<?php
+
+
namespace React\EventLoop;
+
+
use Ev;
+
use EvIo;
+
use EvLoop;
+
use React\EventLoop\Tick\FutureTickQueue;
+
use React\EventLoop\Timer\Timer;
+
use SplObjectStorage;
+
+
/**
+
* An `ext-ev` based event loop.
+
*
+
* This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev),
+
* that provides an interface to `libev` library.
+
* `libev` itself supports a number of system-specific backends (epoll, kqueue).
+
*
+
* This loop is known to work with PHP 5.4 through PHP 8+.
+
*
+
* @see http://php.net/manual/en/book.ev.php
+
* @see https://bitbucket.org/osmanov/pecl-ev/overview
+
*/
+
class ExtEvLoop implements LoopInterface
+
{
+
/**
+
* @var EvLoop
+
*/
+
private $loop;
+
+
/**
+
* @var FutureTickQueue
+
*/
+
private $futureTickQueue;
+
+
/**
+
* @var SplObjectStorage
+
*/
+
private $timers;
+
+
/**
+
* @var EvIo[]
+
*/
+
private $readStreams = array();
+
+
/**
+
* @var EvIo[]
+
*/
+
private $writeStreams = array();
+
+
/**
+
* @var bool
+
*/
+
private $running;
+
+
/**
+
* @var SignalsHandler
+
*/
+
private $signals;
+
+
/**
+
* @var \EvSignal[]
+
*/
+
private $signalEvents = array();
+
+
public function __construct()
+
{
+
$this->loop = new EvLoop();
+
$this->futureTickQueue = new FutureTickQueue();
+
$this->timers = new SplObjectStorage();
+
$this->signals = new SignalsHandler();
+
}
+
+
public function addReadStream($stream, $listener)
+
{
+
$key = (int)$stream;
+
+
if (isset($this->readStreams[$key])) {
+
return;
+
}
+
+
$callback = $this->getStreamListenerClosure($stream, $listener);
+
$event = $this->loop->io($stream, Ev::READ, $callback);
+
$this->readStreams[$key] = $event;
+
}
+
+
/**
+
* @param resource $stream
+
* @param callable $listener
+
*
+
* @return \Closure
+
*/
+
private function getStreamListenerClosure($stream, $listener)
+
{
+
return function () use ($stream, $listener) {
+
\call_user_func($listener, $stream);
+
};
+
}
+
+
public function addWriteStream($stream, $listener)
+
{
+
$key = (int)$stream;
+
+
if (isset($this->writeStreams[$key])) {
+
return;
+
}
+
+
$callback = $this->getStreamListenerClosure($stream, $listener);
+
$event = $this->loop->io($stream, Ev::WRITE, $callback);
+
$this->writeStreams[$key] = $event;
+
}
+
+
public function removeReadStream($stream)
+
{
+
$key = (int)$stream;
+
+
if (!isset($this->readStreams[$key])) {
+
return;
+
}
+
+
$this->readStreams[$key]->stop();
+
unset($this->readStreams[$key]);
+
}
+
+
public function removeWriteStream($stream)
+
{
+
$key = (int)$stream;
+
+
if (!isset($this->writeStreams[$key])) {
+
return;
+
}
+
+
$this->writeStreams[$key]->stop();
+
unset($this->writeStreams[$key]);
+
}
+
+
public function addTimer($interval, $callback)
+
{
+
$timer = new Timer($interval, $callback, false);
+
+
$that = $this;
+
$timers = $this->timers;
+
$callback = function () use ($timer, $timers, $that) {
+
\call_user_func($timer->getCallback(), $timer);
+
+
if ($timers->contains($timer)) {
+
$that->cancelTimer($timer);
+
}
+
};
+
+
$event = $this->loop->timer($timer->getInterval(), 0.0, $callback);
+
$this->timers->attach($timer, $event);
+
+
return $timer;
+
}
+
+
public function addPeriodicTimer($interval, $callback)
+
{
+
$timer = new Timer($interval, $callback, true);
+
+
$callback = function () use ($timer) {
+
\call_user_func($timer->getCallback(), $timer);
+
};
+
+
$event = $this->loop->timer($timer->getInterval(), $timer->getInterval(), $callback);
+
$this->timers->attach($timer, $event);
+
+
return $timer;
+
}
+
+
public function cancelTimer(TimerInterface $timer)
+
{
+
if (!isset($this->timers[$timer])) {
+
return;
+
}
+
+
$event = $this->timers[$timer];
+
$event->stop();
+
$this->timers->detach($timer);
+
}
+
+
public function futureTick($listener)
+
{
+
$this->futureTickQueue->add($listener);
+
}
+
+
public function run()
+
{
+
$this->running = true;
+
+
while ($this->running) {
+
$this->futureTickQueue->tick();
+
+
$hasPendingCallbacks = !$this->futureTickQueue->isEmpty();
+
$wasJustStopped = !$this->running;
+
$nothingLeftToDo = !$this->readStreams
+
&& !$this->writeStreams
+
&& !$this->timers->count()
+
&& $this->signals->isEmpty();
+
+
$flags = Ev::RUN_ONCE;
+
if ($wasJustStopped || $hasPendingCallbacks) {
+
$flags |= Ev::RUN_NOWAIT;
+
} elseif ($nothingLeftToDo) {
+
break;
+
}
+
+
$this->loop->run($flags);
+
}
+
}
+
+
public function stop()
+
{
+
$this->running = false;
+
}
+
+
public function __destruct()
+
{
+
/** @var TimerInterface $timer */
+
foreach ($this->timers as $timer) {
+
$this->cancelTimer($timer);
+
}
+
+
foreach ($this->readStreams as $key => $stream) {
+
$this->removeReadStream($key);
+
}
+
+
foreach ($this->writeStreams as $key => $stream) {
+
$this->removeWriteStream($key);
+
}
+
}
+
+
public function addSignal($signal, $listener)
+
{
+
$this->signals->add($signal, $listener);
+
+
if (!isset($this->signalEvents[$signal])) {
+
$this->signalEvents[$signal] = $this->loop->signal($signal, function() use ($signal) {
+
$this->signals->call($signal);
+
});
+
}
+
}
+
+
public function removeSignal($signal, $listener)
+
{
+
$this->signals->remove($signal, $listener);
+
+
if (isset($this->signalEvents[$signal])) {
+
$this->signalEvents[$signal]->stop();
+
unset($this->signalEvents[$signal]);
+
}
+
}
+
}
+275
vendor/react/event-loop/src/ExtEventLoop.php
···
+
<?php
+
+
namespace React\EventLoop;
+
+
use BadMethodCallException;
+
use Event;
+
use EventBase;
+
use React\EventLoop\Tick\FutureTickQueue;
+
use React\EventLoop\Timer\Timer;
+
use SplObjectStorage;
+
+
/**
+
* An `ext-event` based event loop.
+
*
+
* This uses the [`event` PECL extension](https://pecl.php.net/package/event),
+
* that provides an interface to `libevent` library.
+
* `libevent` itself supports a number of system-specific backends (epoll, kqueue).
+
*
+
* This loop is known to work with PHP 5.4 through PHP 8+.
+
*
+
* @link https://pecl.php.net/package/event
+
*/
+
final class ExtEventLoop implements LoopInterface
+
{
+
private $eventBase;
+
private $futureTickQueue;
+
private $timerCallback;
+
private $timerEvents;
+
private $streamCallback;
+
private $readEvents = array();
+
private $writeEvents = array();
+
private $readListeners = array();
+
private $writeListeners = array();
+
private $readRefs = array();
+
private $writeRefs = array();
+
private $running;
+
private $signals;
+
private $signalEvents = array();
+
+
public function __construct()
+
{
+
if (!\class_exists('EventBase', false)) {
+
throw new BadMethodCallException('Cannot create ExtEventLoop, ext-event extension missing');
+
}
+
+
// support arbitrary file descriptors and not just sockets
+
// Windows only has limited file descriptor support, so do not require this (will fail otherwise)
+
// @link http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html#_setting_up_a_complicated_event_base
+
$config = new \EventConfig();
+
if (\DIRECTORY_SEPARATOR !== '\\') {
+
$config->requireFeatures(\EventConfig::FEATURE_FDS);
+
}
+
+
$this->eventBase = new EventBase($config);
+
$this->futureTickQueue = new FutureTickQueue();
+
$this->timerEvents = new SplObjectStorage();
+
$this->signals = new SignalsHandler();
+
+
$this->createTimerCallback();
+
$this->createStreamCallback();
+
}
+
+
public function __destruct()
+
{
+
// explicitly clear all references to Event objects to prevent SEGFAULTs on Windows
+
foreach ($this->timerEvents as $timer) {
+
$this->timerEvents->detach($timer);
+
}
+
+
$this->readEvents = array();
+
$this->writeEvents = array();
+
}
+
+
public function addReadStream($stream, $listener)
+
{
+
$key = (int) $stream;
+
if (isset($this->readListeners[$key])) {
+
return;
+
}
+
+
$event = new Event($this->eventBase, $stream, Event::PERSIST | Event::READ, $this->streamCallback);
+
$event->add();
+
$this->readEvents[$key] = $event;
+
$this->readListeners[$key] = $listener;
+
+
// ext-event does not increase refcount on stream resources for PHP 7+
+
// manually keep track of stream resource to prevent premature garbage collection
+
if (\PHP_VERSION_ID >= 70000) {
+
$this->readRefs[$key] = $stream;
+
}
+
}
+
+
public function addWriteStream($stream, $listener)
+
{
+
$key = (int) $stream;
+
if (isset($this->writeListeners[$key])) {
+
return;
+
}
+
+
$event = new Event($this->eventBase, $stream, Event::PERSIST | Event::WRITE, $this->streamCallback);
+
$event->add();
+
$this->writeEvents[$key] = $event;
+
$this->writeListeners[$key] = $listener;
+
+
// ext-event does not increase refcount on stream resources for PHP 7+
+
// manually keep track of stream resource to prevent premature garbage collection
+
if (\PHP_VERSION_ID >= 70000) {
+
$this->writeRefs[$key] = $stream;
+
}
+
}
+
+
public function removeReadStream($stream)
+
{
+
$key = (int) $stream;
+
+
if (isset($this->readEvents[$key])) {
+
$this->readEvents[$key]->free();
+
unset(
+
$this->readEvents[$key],
+
$this->readListeners[$key],
+
$this->readRefs[$key]
+
);
+
}
+
}
+
+
public function removeWriteStream($stream)
+
{
+
$key = (int) $stream;
+
+
if (isset($this->writeEvents[$key])) {
+
$this->writeEvents[$key]->free();
+
unset(
+
$this->writeEvents[$key],
+
$this->writeListeners[$key],
+
$this->writeRefs[$key]
+
);
+
}
+
}
+
+
public function addTimer($interval, $callback)
+
{
+
$timer = new Timer($interval, $callback, false);
+
+
$this->scheduleTimer($timer);
+
+
return $timer;
+
}
+
+
public function addPeriodicTimer($interval, $callback)
+
{
+
$timer = new Timer($interval, $callback, true);
+
+
$this->scheduleTimer($timer);
+
+
return $timer;
+
}
+
+
public function cancelTimer(TimerInterface $timer)
+
{
+
if ($this->timerEvents->contains($timer)) {
+
$this->timerEvents[$timer]->free();
+
$this->timerEvents->detach($timer);
+
}
+
}
+
+
public function futureTick($listener)
+
{
+
$this->futureTickQueue->add($listener);
+
}
+
+
public function addSignal($signal, $listener)
+
{
+
$this->signals->add($signal, $listener);
+
+
if (!isset($this->signalEvents[$signal])) {
+
$this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, array($this->signals, 'call'));
+
$this->signalEvents[$signal]->add();
+
}
+
}
+
+
public function removeSignal($signal, $listener)
+
{
+
$this->signals->remove($signal, $listener);
+
+
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
+
$this->signalEvents[$signal]->free();
+
unset($this->signalEvents[$signal]);
+
}
+
}
+
+
public function run()
+
{
+
$this->running = true;
+
+
while ($this->running) {
+
$this->futureTickQueue->tick();
+
+
$flags = EventBase::LOOP_ONCE;
+
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
+
$flags |= EventBase::LOOP_NONBLOCK;
+
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
+
break;
+
}
+
+
$this->eventBase->loop($flags);
+
}
+
}
+
+
public function stop()
+
{
+
$this->running = false;
+
}
+
+
/**
+
* Schedule a timer for execution.
+
*
+
* @param TimerInterface $timer
+
*/
+
private function scheduleTimer(TimerInterface $timer)
+
{
+
$flags = Event::TIMEOUT;
+
+
if ($timer->isPeriodic()) {
+
$flags |= Event::PERSIST;
+
}
+
+
$event = new Event($this->eventBase, -1, $flags, $this->timerCallback, $timer);
+
$this->timerEvents[$timer] = $event;
+
+
$event->add($timer->getInterval());
+
}
+
+
/**
+
* Create a callback used as the target of timer events.
+
*
+
* A reference is kept to the callback for the lifetime of the loop
+
* to prevent "Cannot destroy active lambda function" fatal error from
+
* the event extension.
+
*/
+
private function createTimerCallback()
+
{
+
$timers = $this->timerEvents;
+
$this->timerCallback = function ($_, $__, $timer) use ($timers) {
+
\call_user_func($timer->getCallback(), $timer);
+
+
if (!$timer->isPeriodic() && $timers->contains($timer)) {
+
$this->cancelTimer($timer);
+
}
+
};
+
}
+
+
/**
+
* Create a callback used as the target of stream events.
+
*
+
* A reference is kept to the callback for the lifetime of the loop
+
* to prevent "Cannot destroy active lambda function" fatal error from
+
* the event extension.
+
*/
+
private function createStreamCallback()
+
{
+
$read =& $this->readListeners;
+
$write =& $this->writeListeners;
+
$this->streamCallback = function ($stream, $flags) use (&$read, &$write) {
+
$key = (int) $stream;
+
+
if (Event::READ === (Event::READ & $flags) && isset($read[$key])) {
+
\call_user_func($read[$key], $stream);
+
}
+
+
if (Event::WRITE === (Event::WRITE & $flags) && isset($write[$key])) {
+
\call_user_func($write[$key], $stream);
+
}
+
};
+
}
+
}
+201
vendor/react/event-loop/src/ExtLibevLoop.php
···
+
<?php
+
+
namespace React\EventLoop;
+
+
use BadMethodCallException;
+
use libev\EventLoop;
+
use libev\IOEvent;
+
use libev\SignalEvent;
+
use libev\TimerEvent;
+
use React\EventLoop\Tick\FutureTickQueue;
+
use React\EventLoop\Timer\Timer;
+
use SplObjectStorage;
+
+
/**
+
* [Deprecated] An `ext-libev` based event loop.
+
*
+
* This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev),
+
* that provides an interface to `libev` library.
+
* `libev` itself supports a number of system-specific backends (epoll, kqueue).
+
*
+
* This loop does only work with PHP 5.
+
* An update for PHP 7 is [unlikely](https://github.com/m4rw3r/php-libev/issues/8)
+
* to happen any time soon.
+
*
+
* @see https://github.com/m4rw3r/php-libev
+
* @see https://gist.github.com/1688204
+
* @deprecated 1.2.0, use [`ExtEvLoop`](#extevloop) instead.
+
*/
+
final class ExtLibevLoop implements LoopInterface
+
{
+
private $loop;
+
private $futureTickQueue;
+
private $timerEvents;
+
private $readEvents = array();
+
private $writeEvents = array();
+
private $running;
+
private $signals;
+
private $signalEvents = array();
+
+
public function __construct()
+
{
+
if (!\class_exists('libev\EventLoop', false)) {
+
throw new BadMethodCallException('Cannot create ExtLibevLoop, ext-libev extension missing');
+
}
+
+
$this->loop = new EventLoop();
+
$this->futureTickQueue = new FutureTickQueue();
+
$this->timerEvents = new SplObjectStorage();
+
$this->signals = new SignalsHandler();
+
}
+
+
public function addReadStream($stream, $listener)
+
{
+
if (isset($this->readEvents[(int) $stream])) {
+
return;
+
}
+
+
$callback = function () use ($stream, $listener) {
+
\call_user_func($listener, $stream);
+
};
+
+
$event = new IOEvent($callback, $stream, IOEvent::READ);
+
$this->loop->add($event);
+
+
$this->readEvents[(int) $stream] = $event;
+
}
+
+
public function addWriteStream($stream, $listener)
+
{
+
if (isset($this->writeEvents[(int) $stream])) {
+
return;
+
}
+
+
$callback = function () use ($stream, $listener) {
+
\call_user_func($listener, $stream);
+
};
+
+
$event = new IOEvent($callback, $stream, IOEvent::WRITE);
+
$this->loop->add($event);
+
+
$this->writeEvents[(int) $stream] = $event;
+
}
+
+
public function removeReadStream($stream)
+
{
+
$key = (int) $stream;
+
+
if (isset($this->readEvents[$key])) {
+
$this->readEvents[$key]->stop();
+
$this->loop->remove($this->readEvents[$key]);
+
unset($this->readEvents[$key]);
+
}
+
}
+
+
public function removeWriteStream($stream)
+
{
+
$key = (int) $stream;
+
+
if (isset($this->writeEvents[$key])) {
+
$this->writeEvents[$key]->stop();
+
$this->loop->remove($this->writeEvents[$key]);
+
unset($this->writeEvents[$key]);
+
}
+
}
+
+
public function addTimer($interval, $callback)
+
{
+
$timer = new Timer( $interval, $callback, false);
+
+
$that = $this;
+
$timers = $this->timerEvents;
+
$callback = function () use ($timer, $timers, $that) {
+
\call_user_func($timer->getCallback(), $timer);
+
+
if ($timers->contains($timer)) {
+
$that->cancelTimer($timer);
+
}
+
};
+
+
$event = new TimerEvent($callback, $timer->getInterval());
+
$this->timerEvents->attach($timer, $event);
+
$this->loop->add($event);
+
+
return $timer;
+
}
+
+
public function addPeriodicTimer($interval, $callback)
+
{
+
$timer = new Timer($interval, $callback, true);
+
+
$callback = function () use ($timer) {
+
\call_user_func($timer->getCallback(), $timer);
+
};
+
+
$event = new TimerEvent($callback, $timer->getInterval(), $timer->getInterval());
+
$this->timerEvents->attach($timer, $event);
+
$this->loop->add($event);
+
+
return $timer;
+
}
+
+
public function cancelTimer(TimerInterface $timer)
+
{
+
if (isset($this->timerEvents[$timer])) {
+
$this->loop->remove($this->timerEvents[$timer]);
+
$this->timerEvents->detach($timer);
+
}
+
}
+
+
public function futureTick($listener)
+
{
+
$this->futureTickQueue->add($listener);
+
}
+
+
public function addSignal($signal, $listener)
+
{
+
$this->signals->add($signal, $listener);
+
+
if (!isset($this->signalEvents[$signal])) {
+
$signals = $this->signals;
+
$this->signalEvents[$signal] = new SignalEvent(function () use ($signals, $signal) {
+
$signals->call($signal);
+
}, $signal);
+
$this->loop->add($this->signalEvents[$signal]);
+
}
+
}
+
+
public function removeSignal($signal, $listener)
+
{
+
$this->signals->remove($signal, $listener);
+
+
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
+
$this->signalEvents[$signal]->stop();
+
$this->loop->remove($this->signalEvents[$signal]);
+
unset($this->signalEvents[$signal]);
+
}
+
}
+
+
public function run()
+
{
+
$this->running = true;
+
+
while ($this->running) {
+
$this->futureTickQueue->tick();
+
+
$flags = EventLoop::RUN_ONCE;
+
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
+
$flags |= EventLoop::RUN_NOWAIT;
+
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
+
break;
+
}
+
+
$this->loop->run($flags);
+
}
+
}
+
+
public function stop()
+
{
+
$this->running = false;
+
}
+
}
+285
vendor/react/event-loop/src/ExtLibeventLoop.php
···
+
<?php
+
+
namespace React\EventLoop;
+
+
use BadMethodCallException;
+
use Event;
+
use EventBase;
+
use React\EventLoop\Tick\FutureTickQueue;
+
use React\EventLoop\Timer\Timer;
+
use SplObjectStorage;
+
+
/**
+
* [Deprecated] An `ext-libevent` based event loop.
+
*
+
* This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent),
+
* that provides an interface to `libevent` library.
+
* `libevent` itself supports a number of system-specific backends (epoll, kqueue).
+
*
+
* This event loop does only work with PHP 5.
+
* An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for
+
* PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s.
+
* To reiterate: Using this event loop on PHP 7 is not recommended.
+
* Accordingly, neither the [`Loop` class](#loop) nor the deprecated
+
* [`Factory` class](#factory) will try to use this event loop on PHP 7.
+
*
+
* This event loop is known to trigger a readable listener only if
+
* the stream *becomes* readable (edge-triggered) and may not trigger if the
+
* stream has already been readable from the beginning.
+
* This also implies that a stream may not be recognized as readable when data
+
* is still left in PHP's internal stream buffers.
+
* As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
+
* to disable PHP's internal read buffer in this case.
+
* See also [`addReadStream()`](#addreadstream) for more details.
+
*
+
* @link https://pecl.php.net/package/libevent
+
* @deprecated 1.2.0, use [`ExtEventLoop`](#exteventloop) instead.
+
*/
+
final class ExtLibeventLoop implements LoopInterface
+
{
+
/** @internal */
+
const MICROSECONDS_PER_SECOND = 1000000;
+
+
private $eventBase;
+
private $futureTickQueue;
+
private $timerCallback;
+
private $timerEvents;
+
private $streamCallback;
+
private $readEvents = array();
+
private $writeEvents = array();
+
private $readListeners = array();
+
private $writeListeners = array();
+
private $running;
+
private $signals;
+
private $signalEvents = array();
+
+
public function __construct()
+
{
+
if (!\function_exists('event_base_new')) {
+
throw new BadMethodCallException('Cannot create ExtLibeventLoop, ext-libevent extension missing');
+
}
+
+
$this->eventBase = \event_base_new();
+
$this->futureTickQueue = new FutureTickQueue();
+
$this->timerEvents = new SplObjectStorage();
+
$this->signals = new SignalsHandler();
+
+
$this->createTimerCallback();
+
$this->createStreamCallback();
+
}
+
+
public function addReadStream($stream, $listener)
+
{
+
$key = (int) $stream;
+
if (isset($this->readListeners[$key])) {
+
return;
+
}
+
+
$event = \event_new();
+
\event_set($event, $stream, \EV_PERSIST | \EV_READ, $this->streamCallback);
+
\event_base_set($event, $this->eventBase);
+
\event_add($event);
+
+
$this->readEvents[$key] = $event;
+
$this->readListeners[$key] = $listener;
+
}
+
+
public function addWriteStream($stream, $listener)
+
{
+
$key = (int) $stream;
+
if (isset($this->writeListeners[$key])) {
+
return;
+
}
+
+
$event = \event_new();
+
\event_set($event, $stream, \EV_PERSIST | \EV_WRITE, $this->streamCallback);
+
\event_base_set($event, $this->eventBase);
+
\event_add($event);
+
+
$this->writeEvents[$key] = $event;
+
$this->writeListeners[$key] = $listener;
+
}
+
+
public function removeReadStream($stream)
+
{
+
$key = (int) $stream;
+
+
if (isset($this->readListeners[$key])) {
+
$event = $this->readEvents[$key];
+
\event_del($event);
+
\event_free($event);
+
+
unset(
+
$this->readEvents[$key],
+
$this->readListeners[$key]
+
);
+
}
+
}
+
+
public function removeWriteStream($stream)
+
{
+
$key = (int) $stream;
+
+
if (isset($this->writeListeners[$key])) {
+
$event = $this->writeEvents[$key];
+
\event_del($event);
+
\event_free($event);
+
+
unset(
+
$this->writeEvents[$key],
+
$this->writeListeners[$key]
+
);
+
}
+
}
+
+
public function addTimer($interval, $callback)
+
{
+
$timer = new Timer($interval, $callback, false);
+
+
$this->scheduleTimer($timer);
+
+
return $timer;
+
}
+
+
public function addPeriodicTimer($interval, $callback)
+
{
+
$timer = new Timer($interval, $callback, true);
+
+
$this->scheduleTimer($timer);
+
+
return $timer;
+
}
+
+
public function cancelTimer(TimerInterface $timer)
+
{
+
if ($this->timerEvents->contains($timer)) {
+
$event = $this->timerEvents[$timer];
+
\event_del($event);
+
\event_free($event);
+
+
$this->timerEvents->detach($timer);
+
}
+
}
+
+
public function futureTick($listener)
+
{
+
$this->futureTickQueue->add($listener);
+
}
+
+
public function addSignal($signal, $listener)
+
{
+
$this->signals->add($signal, $listener);
+
+
if (!isset($this->signalEvents[$signal])) {
+
$this->signalEvents[$signal] = \event_new();
+
\event_set($this->signalEvents[$signal], $signal, \EV_PERSIST | \EV_SIGNAL, array($this->signals, 'call'));
+
\event_base_set($this->signalEvents[$signal], $this->eventBase);
+
\event_add($this->signalEvents[$signal]);
+
}
+
}
+
+
public function removeSignal($signal, $listener)
+
{
+
$this->signals->remove($signal, $listener);
+
+
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
+
\event_del($this->signalEvents[$signal]);
+
\event_free($this->signalEvents[$signal]);
+
unset($this->signalEvents[$signal]);
+
}
+
}
+
+
public function run()
+
{
+
$this->running = true;
+
+
while ($this->running) {
+
$this->futureTickQueue->tick();
+
+
$flags = \EVLOOP_ONCE;
+
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
+
$flags |= \EVLOOP_NONBLOCK;
+
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
+
break;
+
}
+
+
\event_base_loop($this->eventBase, $flags);
+
}
+
}
+
+
public function stop()
+
{
+
$this->running = false;
+
}
+
+
/**
+
* Schedule a timer for execution.
+
*
+
* @param TimerInterface $timer
+
*/
+
private function scheduleTimer(TimerInterface $timer)
+
{
+
$this->timerEvents[$timer] = $event = \event_timer_new();
+
+
\event_timer_set($event, $this->timerCallback, $timer);
+
\event_base_set($event, $this->eventBase);
+
\event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND);
+
}
+
+
/**
+
* Create a callback used as the target of timer events.
+
*
+
* A reference is kept to the callback for the lifetime of the loop
+
* to prevent "Cannot destroy active lambda function" fatal error from
+
* the event extension.
+
*/
+
private function createTimerCallback()
+
{
+
$that = $this;
+
$timers = $this->timerEvents;
+
$this->timerCallback = function ($_, $__, $timer) use ($timers, $that) {
+
\call_user_func($timer->getCallback(), $timer);
+
+
// Timer already cancelled ...
+
if (!$timers->contains($timer)) {
+
return;
+
}
+
+
// Reschedule periodic timers ...
+
if ($timer->isPeriodic()) {
+
\event_add(
+
$timers[$timer],
+
$timer->getInterval() * ExtLibeventLoop::MICROSECONDS_PER_SECOND
+
);
+
+
// Clean-up one shot timers ...
+
} else {
+
$that->cancelTimer($timer);
+
}
+
};
+
}
+
+
/**
+
* Create a callback used as the target of stream events.
+
*
+
* A reference is kept to the callback for the lifetime of the loop
+
* to prevent "Cannot destroy active lambda function" fatal error from
+
* the event extension.
+
*/
+
private function createStreamCallback()
+
{
+
$read =& $this->readListeners;
+
$write =& $this->writeListeners;
+
$this->streamCallback = function ($stream, $flags) use (&$read, &$write) {
+
$key = (int) $stream;
+
+
if (\EV_READ === (\EV_READ & $flags) && isset($read[$key])) {
+
\call_user_func($read[$key], $stream);
+
}
+
+
if (\EV_WRITE === (\EV_WRITE & $flags) && isset($write[$key])) {
+
\call_user_func($write[$key], $stream);
+
}
+
};
+
}
+
}
+342
vendor/react/event-loop/src/ExtUvLoop.php
···
+
<?php
+
+
namespace React\EventLoop;
+
+
use React\EventLoop\Tick\FutureTickQueue;
+
use React\EventLoop\Timer\Timer;
+
use SplObjectStorage;
+
+
/**
+
* An `ext-uv` based event loop.
+
*
+
* This loop uses the [`uv` PECL extension](https://pecl.php.net/package/uv),
+
* that provides an interface to `libuv` library.
+
* `libuv` itself supports a number of system-specific backends (epoll, kqueue).
+
*
+
* This loop is known to work with PHP 7+.
+
*
+
* @see https://github.com/bwoebi/php-uv
+
*/
+
final class ExtUvLoop implements LoopInterface
+
{
+
private $uv;
+
private $futureTickQueue;
+
private $timers;
+
private $streamEvents = array();
+
private $readStreams = array();
+
private $writeStreams = array();
+
private $running;
+
private $signals;
+
private $signalEvents = array();
+
private $streamListener;
+
+
public function __construct()
+
{
+
if (!\function_exists('uv_loop_new')) {
+
throw new \BadMethodCallException('Cannot create LibUvLoop, ext-uv extension missing');
+
}
+
+
$this->uv = \uv_loop_new();
+
$this->futureTickQueue = new FutureTickQueue();
+
$this->timers = new SplObjectStorage();
+
$this->streamListener = $this->createStreamListener();
+
$this->signals = new SignalsHandler();
+
}
+
+
/**
+
* Returns the underlying ext-uv event loop. (Internal ReactPHP use only.)
+
*
+
* @internal
+
*
+
* @return resource
+
*/
+
public function getUvLoop()
+
{
+
return $this->uv;
+
}
+
+
/**
+
* {@inheritdoc}
+
*/
+
public function addReadStream($stream, $listener)
+
{
+
if (isset($this->readStreams[(int) $stream])) {
+
return;
+
}
+
+
$this->readStreams[(int) $stream] = $listener;
+
$this->addStream($stream);
+
}
+
+
/**
+
* {@inheritdoc}
+
*/
+
public function addWriteStream($stream, $listener)
+
{
+
if (isset($this->writeStreams[(int) $stream])) {
+
return;
+
}
+
+
$this->writeStreams[(int) $stream] = $listener;
+
$this->addStream($stream);
+
}
+
+
/**
+
* {@inheritdoc}
+
*/
+
public function removeReadStream($stream)
+
{
+
if (!isset($this->streamEvents[(int) $stream])) {
+
return;
+
}
+
+
unset($this->readStreams[(int) $stream]);
+
$this->removeStream($stream);
+
}
+
+
/**
+
* {@inheritdoc}
+
*/
+
public function removeWriteStream($stream)
+
{
+
if (!isset($this->streamEvents[(int) $stream])) {
+
return;
+
}
+
+
unset($this->writeStreams[(int) $stream]);
+
$this->removeStream($stream);
+
}
+
+
/**
+
* {@inheritdoc}
+
*/
+
public function addTimer($interval, $callback)
+
{
+
$timer = new Timer($interval, $callback, false);
+
+
$that = $this;
+
$timers = $this->timers;
+
$callback = function () use ($timer, $timers, $that) {
+
\call_user_func($timer->getCallback(), $timer);
+
+
if ($timers->contains($timer)) {
+
$that->cancelTimer($timer);
+
}
+
};
+
+
$event = \uv_timer_init($this->uv);
+
$this->timers->attach($timer, $event);
+
\uv_timer_start(
+
$event,
+
$this->convertFloatSecondsToMilliseconds($interval),
+
0,
+
$callback
+
);
+
+
return $timer;
+
}
+
+
/**
+
* {@inheritdoc}
+
*/
+
public function addPeriodicTimer($interval, $callback)
+
{
+
$timer = new Timer($interval, $callback, true);
+
+
$callback = function () use ($timer) {
+
\call_user_func($timer->getCallback(), $timer);
+
};
+
+
$interval = $this->convertFloatSecondsToMilliseconds($interval);
+
$event = \uv_timer_init($this->uv);
+
$this->timers->attach($timer, $event);
+
\uv_timer_start(
+
$event,
+
$interval,
+
(int) $interval === 0 ? 1 : $interval,
+
$callback
+
);
+
+
return $timer;
+
}
+
+
/**
+
* {@inheritdoc}
+
*/
+
public function cancelTimer(TimerInterface $timer)
+
{
+
if (isset($this->timers[$timer])) {
+
@\uv_timer_stop($this->timers[$timer]);
+
$this->timers->detach($timer);
+
}
+
}
+
+
/**
+
* {@inheritdoc}
+
*/
+
public function futureTick($listener)
+
{
+
$this->futureTickQueue->add($listener);
+
}
+
+
public function addSignal($signal, $listener)
+
{
+
$this->signals->add($signal, $listener);
+
+
if (!isset($this->signalEvents[$signal])) {
+
$signals = $this->signals;
+
$this->signalEvents[$signal] = \uv_signal_init($this->uv);
+
\uv_signal_start($this->signalEvents[$signal], function () use ($signals, $signal) {
+
$signals->call($signal);
+
}, $signal);
+
}
+
}
+
+
public function removeSignal($signal, $listener)
+
{
+
$this->signals->remove($signal, $listener);
+
+
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
+
\uv_signal_stop($this->signalEvents[$signal]);
+
unset($this->signalEvents[$signal]);
+
}
+
}
+
+
/**
+
* {@inheritdoc}
+
*/
+
public function run()
+
{
+
$this->running = true;
+
+
while ($this->running) {
+
$this->futureTickQueue->tick();
+
+
$hasPendingCallbacks = !$this->futureTickQueue->isEmpty();
+
$wasJustStopped = !$this->running;
+
$nothingLeftToDo = !$this->readStreams
+
&& !$this->writeStreams
+
&& !$this->timers->count()
+
&& $this->signals->isEmpty();
+
+
// Use UV::RUN_ONCE when there are only I/O events active in the loop and block until one of those triggers,
+
// otherwise use UV::RUN_NOWAIT.
+
// @link http://docs.libuv.org/en/v1.x/loop.html#c.uv_run
+
$flags = \UV::RUN_ONCE;
+
if ($wasJustStopped || $hasPendingCallbacks) {
+
$flags = \UV::RUN_NOWAIT;
+
} elseif ($nothingLeftToDo) {
+
break;
+
}
+
+
\uv_run($this->uv, $flags);
+
}
+
}
+
+
/**
+
* {@inheritdoc}
+
*/
+
public function stop()
+
{
+
$this->running = false;
+
}
+
+
private function addStream($stream)
+
{
+
if (!isset($this->streamEvents[(int) $stream])) {
+
$this->streamEvents[(int)$stream] = \uv_poll_init_socket($this->uv, $stream);
+
}
+
+
if ($this->streamEvents[(int) $stream] !== false) {
+
$this->pollStream($stream);
+
}
+
}
+
+
private function removeStream($stream)
+
{
+
if (!isset($this->streamEvents[(int) $stream])) {
+
return;
+
}
+
+
if (!isset($this->readStreams[(int) $stream])
+
&& !isset($this->writeStreams[(int) $stream])) {
+
\uv_poll_stop($this->streamEvents[(int) $stream]);
+
\uv_close($this->streamEvents[(int) $stream]);
+
unset($this->streamEvents[(int) $stream]);
+
return;
+
}
+
+
$this->pollStream($stream);
+
}
+
+
private function pollStream($stream)
+
{
+
if (!isset($this->streamEvents[(int) $stream])) {
+
return;
+
}
+
+
$flags = 0;
+
if (isset($this->readStreams[(int) $stream])) {
+
$flags |= \UV::READABLE;
+
}
+
+
if (isset($this->writeStreams[(int) $stream])) {
+
$flags |= \UV::WRITABLE;
+
}
+
+
\uv_poll_start($this->streamEvents[(int) $stream], $flags, $this->streamListener);
+
}
+
+
/**
+
* Create a stream listener
+
*
+
* @return callable Returns a callback
+
*/
+
private function createStreamListener()
+
{
+
$callback = function ($event, $status, $events, $stream) {
+
// libuv automatically stops polling on error, re-enable polling to match other loop implementations
+
if ($status !== 0) {
+
$this->pollStream($stream);
+
+
// libuv may report no events on error, but this should still invoke stream listeners to report closed connections
+
// re-enable both readable and writable, correct listeners will be checked below anyway
+
if ($events === 0) {
+
$events = \UV::READABLE | \UV::WRITABLE;
+
}
+
}
+
+
if (isset($this->readStreams[(int) $stream]) && ($events & \UV::READABLE)) {
+
\call_user_func($this->readStreams[(int) $stream], $stream);
+
}
+
+
if (isset($this->writeStreams[(int) $stream]) && ($events & \UV::WRITABLE)) {
+
\call_user_func($this->writeStreams[(int) $stream], $stream);
+
}
+
};
+
+
return $callback;
+
}
+
+
/**
+
* @param float $interval
+
* @return int
+
*/
+
private function convertFloatSecondsToMilliseconds($interval)
+
{
+
if ($interval < 0) {
+
return 0;
+
}
+
+
$maxValue = (int) (\PHP_INT_MAX / 1000);
+
$intInterval = (int) $interval;
+
+
if (($intInterval <= 0 && $interval > 1) || $intInterval >= $maxValue) {
+
throw new \InvalidArgumentException(
+
"Interval overflow, value must be lower than '{$maxValue}', but '{$interval}' passed."
+
);
+
}
+
+
return (int) \floor($interval * 1000);
+
}
+
}
+75
vendor/react/event-loop/src/Factory.php
···
+
<?php
+
+
namespace React\EventLoop;
+
+
/**
+
* [Deprecated] The `Factory` class exists as a convenient way to pick the best available event loop implementation.
+
*
+
* @deprecated 1.2.0 See Loop instead.
+
* @see Loop
+
*/
+
final class Factory
+
{
+
/**
+
* [Deprecated] Creates a new event loop instance
+
*
+
* ```php
+
* // deprecated
+
* $loop = React\EventLoop\Factory::create();
+
*
+
* // new
+
* $loop = React\EventLoop\Loop::get();
+
* ```
+
*
+
* This method always returns an instance implementing `LoopInterface`,
+
* the actual event loop implementation is an implementation detail.
+
*
+
* This method should usually only be called once at the beginning of the program.
+
*
+
* @deprecated 1.2.0 See Loop::get() instead.
+
* @see Loop::get()
+
*
+
* @return LoopInterface
+
*/
+
public static function create()
+
{
+
$loop = self::construct();
+
+
Loop::set($loop);
+
+
return $loop;
+
}
+
+
/**
+
* @internal
+
* @return LoopInterface
+
*/
+
private static function construct()
+
{
+
// @codeCoverageIgnoreStart
+
if (\function_exists('uv_loop_new')) {
+
// only use ext-uv on PHP 7
+
return new ExtUvLoop();
+
}
+
+
if (\class_exists('libev\EventLoop', false)) {
+
return new ExtLibevLoop();
+
}
+
+
if (\class_exists('EvLoop', false)) {
+
return new ExtEvLoop();
+
}
+
+
if (\class_exists('EventBase', false)) {
+
return new ExtEventLoop();
+
}
+
+
if (\function_exists('event_base_new') && \PHP_MAJOR_VERSION === 5) {
+
// only use ext-libevent on PHP 5 for now
+
return new ExtLibeventLoop();
+
}
+
+
return new StreamSelectLoop();
+
// @codeCoverageIgnoreEnd
+
}
+
}
+266
vendor/react/event-loop/src/Loop.php
···
+
<?php
+
+
namespace React\EventLoop;
+
+
/**
+
* The `Loop` class exists as a convenient way to get the currently relevant loop
+
*/
+
final class Loop
+
{
+
/**
+
* @var ?LoopInterface
+
*/
+
private static $instance;
+
+
/** @var bool */
+
private static $stopped = false;
+
+
/**
+
* Returns the event loop.
+
* When no loop is set, it will call the factory to create one.
+
*
+
* This method always returns an instance implementing `LoopInterface`,
+
* the actual event loop implementation is an implementation detail.
+
*
+
* This method is the preferred way to get the event loop and using
+
* Factory::create has been deprecated.
+
*
+
* @return LoopInterface
+
*/
+
public static function get()
+
{
+
if (self::$instance instanceof LoopInterface) {
+
return self::$instance;
+
}
+
+
self::$instance = $loop = Factory::create();
+
+
// Automatically run loop at end of program, unless already started or stopped explicitly.
+
// This is tested using child processes, so coverage is actually 100%, see BinTest.
+
// @codeCoverageIgnoreStart
+
$hasRun = false;
+
$loop->futureTick(function () use (&$hasRun) {
+
$hasRun = true;
+
});
+
+
$stopped =& self::$stopped;
+
register_shutdown_function(function () use ($loop, &$hasRun, &$stopped) {
+
// Don't run if we're coming from a fatal error (uncaught exception).
+
$error = error_get_last();
+
if ((isset($error['type']) ? $error['type'] : 0) & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR)) {
+
return;
+
}
+
+
if (!$hasRun && !$stopped) {
+
$loop->run();
+
}
+
});
+
// @codeCoverageIgnoreEnd
+
+
return self::$instance;
+
}
+
+
/**
+
* Internal undocumented method, behavior might change or throw in the
+
* future. Use with caution and at your own risk.
+
*
+
* @internal
+
* @return void
+
*/
+
public static function set(LoopInterface $loop)
+
{
+
self::$instance = $loop;
+
}
+
+
/**
+
* [Advanced] Register a listener to be notified when a stream is ready to read.
+
*
+
* @param resource $stream
+
* @param callable $listener
+
* @return void
+
* @throws \Exception
+
* @see LoopInterface::addReadStream()
+
*/
+
public static function addReadStream($stream, $listener)
+
{
+
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
+
if (self::$instance === null) {
+
self::get();
+
}
+
self::$instance->addReadStream($stream, $listener);
+
}
+
+
/**
+
* [Advanced] Register a listener to be notified when a stream is ready to write.
+
*
+
* @param resource $stream
+
* @param callable $listener
+
* @return void
+
* @throws \Exception
+
* @see LoopInterface::addWriteStream()
+
*/
+
public static function addWriteStream($stream, $listener)
+
{
+
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
+
if (self::$instance === null) {
+
self::get();
+
}
+
self::$instance->addWriteStream($stream, $listener);
+
}
+
+
/**
+
* Remove the read event listener for the given stream.
+
*
+
* @param resource $stream
+
* @return void
+
* @see LoopInterface::removeReadStream()
+
*/
+
public static function removeReadStream($stream)
+
{
+
if (self::$instance !== null) {
+
self::$instance->removeReadStream($stream);
+
}
+
}
+
+
/**
+
* Remove the write event listener for the given stream.
+
*
+
* @param resource $stream
+
* @return void
+
* @see LoopInterface::removeWriteStream()
+
*/
+
public static function removeWriteStream($stream)
+
{
+
if (self::$instance !== null) {
+
self::$instance->removeWriteStream($stream);
+
}
+
}
+
+
/**
+
* Enqueue a callback to be invoked once after the given interval.
+
*
+
* @param float $interval
+
* @param callable $callback
+
* @return TimerInterface
+
* @see LoopInterface::addTimer()
+
*/
+
public static function addTimer($interval, $callback)
+
{
+
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
+
if (self::$instance === null) {
+
self::get();
+
}
+
return self::$instance->addTimer($interval, $callback);
+
}
+
+
/**
+
* Enqueue a callback to be invoked repeatedly after the given interval.
+
*
+
* @param float $interval
+
* @param callable $callback
+
* @return TimerInterface
+
* @see LoopInterface::addPeriodicTimer()
+
*/
+
public static function addPeriodicTimer($interval, $callback)
+
{
+
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
+
if (self::$instance === null) {
+
self::get();
+
}
+
return self::$instance->addPeriodicTimer($interval, $callback);
+
}
+
+
/**
+
* Cancel a pending timer.
+
*
+
* @param TimerInterface $timer
+
* @return void
+
* @see LoopInterface::cancelTimer()
+
*/
+
public static function cancelTimer(TimerInterface $timer)
+
{
+
if (self::$instance !== null) {
+
self::$instance->cancelTimer($timer);
+
}
+
}
+
+
/**
+
* Schedule a callback to be invoked on a future tick of the event loop.
+
*
+
* @param callable $listener
+
* @return void
+
* @see LoopInterface::futureTick()
+
*/
+
public static function futureTick($listener)
+
{
+
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
+
if (self::$instance === null) {
+
self::get();
+
}
+
+
self::$instance->futureTick($listener);
+
}
+
+
/**
+
* Register a listener to be notified when a signal has been caught by this process.
+
*
+
* @param int $signal
+
* @param callable $listener
+
* @return void
+
* @see LoopInterface::addSignal()
+
*/
+
public static function addSignal($signal, $listener)
+
{
+
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
+
if (self::$instance === null) {
+
self::get();
+
}
+
+
self::$instance->addSignal($signal, $listener);
+
}
+
+
/**
+
* Removes a previously added signal listener.
+
*
+
* @param int $signal
+
* @param callable $listener
+
* @return void
+
* @see LoopInterface::removeSignal()
+
*/
+
public static function removeSignal($signal, $listener)
+
{
+
if (self::$instance !== null) {
+
self::$instance->removeSignal($signal, $listener);
+
}
+
}
+
+
/**
+
* Run the event loop until there are no more tasks to perform.
+
*
+
* @return void
+
* @see LoopInterface::run()
+
*/
+
public static function run()
+
{
+
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
+
if (self::$instance === null) {
+
self::get();
+
}
+
+
self::$instance->run();
+
}
+
+
/**
+
* Instruct a running event loop to stop.
+
*
+
* @return void
+
* @see LoopInterface::stop()
+
*/
+
public static function stop()
+
{
+
self::$stopped = true;
+
if (self::$instance !== null) {
+
self::$instance->stop();
+
}
+
}
+
}
+472
vendor/react/event-loop/src/LoopInterface.php
···
+
<?php
+
+
namespace React\EventLoop;
+
+
interface LoopInterface
+
{
+
/**
+
* [Advanced] Register a listener to be notified when a stream is ready to read.
+
*
+
* Note that this low-level API is considered advanced usage.
+
* Most use cases should probably use the higher-level
+
* [readable Stream API](https://github.com/reactphp/stream#readablestreaminterface)
+
* instead.
+
*
+
* The first parameter MUST be a valid stream resource that supports
+
* checking whether it is ready to read by this loop implementation.
+
* A single stream resource MUST NOT be added more than once.
+
* Instead, either call [`removeReadStream()`](#removereadstream) first or
+
* react to this event with a single listener and then dispatch from this
+
* listener. This method MAY throw an `Exception` if the given resource type
+
* is not supported by this loop implementation.
+
*
+
* The second parameter MUST be a listener callback function that accepts
+
* the stream resource as its only parameter.
+
* If you don't use the stream resource inside your listener callback function
+
* you MAY use a function which has no parameters at all.
+
*
+
* The listener callback function MUST NOT throw an `Exception`.
+
* The return value of the listener callback function will be ignored and has
+
* no effect, so for performance reasons you're recommended to not return
+
* any excessive data structures.
+
*
+
* If you want to access any variables within your callback function, you
+
* can bind arbitrary data to a callback closure like this:
+
*
+
* ```php
+
* $loop->addReadStream($stream, function ($stream) use ($name) {
+
* echo $name . ' said: ' . fread($stream);
+
* });
+
* ```
+
*
+
* See also [example #11](examples).
+
*
+
* You can invoke [`removeReadStream()`](#removereadstream) to remove the
+
* read event listener for this stream.
+
*
+
* The execution order of listeners when multiple streams become ready at
+
* the same time is not guaranteed.
+
*
+
* @param resource $stream The PHP stream resource to check.
+
* @param callable $listener Invoked when the stream is ready.
+
* @throws \Exception if the given resource type is not supported by this loop implementation
+
* @see self::removeReadStream()
+
*/
+
public function addReadStream($stream, $listener);
+
+
/**
+
* [Advanced] Register a listener to be notified when a stream is ready to write.
+
*
+
* Note that this low-level API is considered advanced usage.
+
* Most use cases should probably use the higher-level
+
* [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface)
+
* instead.
+
*
+
* The first parameter MUST be a valid stream resource that supports
+
* checking whether it is ready to write by this loop implementation.
+
* A single stream resource MUST NOT be added more than once.
+
* Instead, either call [`removeWriteStream()`](#removewritestream) first or
+
* react to this event with a single listener and then dispatch from this
+
* listener. This method MAY throw an `Exception` if the given resource type
+
* is not supported by this loop implementation.
+
*
+
* The second parameter MUST be a listener callback function that accepts
+
* the stream resource as its only parameter.
+
* If you don't use the stream resource inside your listener callback function
+
* you MAY use a function which has no parameters at all.
+
*
+
* The listener callback function MUST NOT throw an `Exception`.
+
* The return value of the listener callback function will be ignored and has
+
* no effect, so for performance reasons you're recommended to not return
+
* any excessive data structures.
+
*
+
* If you want to access any variables within your callback function, you
+
* can bind arbitrary data to a callback closure like this:
+
*
+
* ```php
+
* $loop->addWriteStream($stream, function ($stream) use ($name) {
+
* fwrite($stream, 'Hello ' . $name);
+
* });
+
* ```
+
*
+
* See also [example #12](examples).
+
*
+
* You can invoke [`removeWriteStream()`](#removewritestream) to remove the
+
* write event listener for this stream.
+
*
+
* The execution order of listeners when multiple streams become ready at
+
* the same time is not guaranteed.
+
*
+
* Some event loop implementations are known to only trigger the listener if
+
* the stream *becomes* readable (edge-triggered) and may not trigger if the
+
* stream has already been readable from the beginning.
+
* This also implies that a stream may not be recognized as readable when data
+
* is still left in PHP's internal stream buffers.
+
* As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
+
* to disable PHP's internal read buffer in this case.
+
*
+
* @param resource $stream The PHP stream resource to check.
+
* @param callable $listener Invoked when the stream is ready.
+
* @throws \Exception if the given resource type is not supported by this loop implementation
+
* @see self::removeWriteStream()
+
*/
+
public function addWriteStream($stream, $listener);
+
+
/**
+
* Remove the read event listener for the given stream.
+
*
+
* Removing a stream from the loop that has already been removed or trying
+
* to remove a stream that was never added or is invalid has no effect.
+
*
+
* @param resource $stream The PHP stream resource.
+
*/
+
public function removeReadStream($stream);
+
+
/**
+
* Remove the write event listener for the given stream.
+
*
+
* Removing a stream from the loop that has already been removed or trying
+
* to remove a stream that was never added or is invalid has no effect.
+
*
+
* @param resource $stream The PHP stream resource.
+
*/
+
public function removeWriteStream($stream);
+
+
/**
+
* Enqueue a callback to be invoked once after the given interval.
+
*
+
* The second parameter MUST be a timer callback function that accepts
+
* the timer instance as its only parameter.
+
* If you don't use the timer instance inside your timer callback function
+
* you MAY use a function which has no parameters at all.
+
*
+
* The timer callback function MUST NOT throw an `Exception`.
+
* The return value of the timer callback function will be ignored and has
+
* no effect, so for performance reasons you're recommended to not return
+
* any excessive data structures.
+
*
+
* This method returns a timer instance. The same timer instance will also be
+
* passed into the timer callback function as described above.
+
* You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer.
+
* Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure
+
* the callback will be invoked only once after the given interval.
+
*
+
* ```php
+
* $loop->addTimer(0.8, function () {
+
* echo 'world!' . PHP_EOL;
+
* });
+
*
+
* $loop->addTimer(0.3, function () {
+
* echo 'hello ';
+
* });
+
* ```
+
*
+
* See also [example #1](examples).
+
*
+
* If you want to access any variables within your callback function, you
+
* can bind arbitrary data to a callback closure like this:
+
*
+
* ```php
+
* function hello($name, LoopInterface $loop)
+
* {
+
* $loop->addTimer(1.0, function () use ($name) {
+
* echo "hello $name\n";
+
* });
+
* }
+
*
+
* hello('Tester', $loop);
+
* ```
+
*
+
* This interface does not enforce any particular timer resolution, so
+
* special care may have to be taken if you rely on very high precision with
+
* millisecond accuracy or below. Event loop implementations SHOULD work on
+
* a best effort basis and SHOULD provide at least millisecond accuracy
+
* unless otherwise noted. Many existing event loop implementations are
+
* known to provide microsecond accuracy, but it's generally not recommended
+
* to rely on this high precision.
+
*
+
* Similarly, the execution order of timers scheduled to execute at the
+
* same time (within its possible accuracy) is not guaranteed.
+
*
+
* This interface suggests that event loop implementations SHOULD use a
+
* monotonic time source if available. Given that a monotonic time source is
+
* only available as of PHP 7.3 by default, event loop implementations MAY
+
* fall back to using wall-clock time.
+
* While this does not affect many common use cases, this is an important
+
* distinction for programs that rely on a high time precision or on systems
+
* that are subject to discontinuous time adjustments (time jumps).
+
* This means that if you schedule a timer to trigger in 30s and then adjust
+
* your system time forward by 20s, the timer SHOULD still trigger in 30s.
+
* See also [event loop implementations](#loop-implementations) for more details.
+
*
+
* @param int|float $interval The number of seconds to wait before execution.
+
* @param callable $callback The callback to invoke.
+
*
+
* @return TimerInterface
+
*/
+
public function addTimer($interval, $callback);
+
+
/**
+
* Enqueue a callback to be invoked repeatedly after the given interval.
+
*
+
* The second parameter MUST be a timer callback function that accepts
+
* the timer instance as its only parameter.
+
* If you don't use the timer instance inside your timer callback function
+
* you MAY use a function which has no parameters at all.
+
*
+
* The timer callback function MUST NOT throw an `Exception`.
+
* The return value of the timer callback function will be ignored and has
+
* no effect, so for performance reasons you're recommended to not return
+
* any excessive data structures.
+
*
+
* This method returns a timer instance. The same timer instance will also be
+
* passed into the timer callback function as described above.
+
* Unlike [`addTimer()`](#addtimer), this method will ensure the callback
+
* will be invoked infinitely after the given interval or until you invoke
+
* [`cancelTimer`](#canceltimer).
+
*
+
* ```php
+
* $timer = $loop->addPeriodicTimer(0.1, function () {
+
* echo 'tick!' . PHP_EOL;
+
* });
+
*
+
* $loop->addTimer(1.0, function () use ($loop, $timer) {
+
* $loop->cancelTimer($timer);
+
* echo 'Done' . PHP_EOL;
+
* });
+
* ```
+
*
+
* See also [example #2](examples).
+
*
+
* If you want to limit the number of executions, you can bind
+
* arbitrary data to a callback closure like this:
+
*
+
* ```php
+
* function hello($name, LoopInterface $loop)
+
* {
+
* $n = 3;
+
* $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) {
+
* if ($n > 0) {
+
* --$n;
+
* echo "hello $name\n";
+
* } else {
+
* $loop->cancelTimer($timer);
+
* }
+
* });
+
* }
+
*
+
* hello('Tester', $loop);
+
* ```
+
*
+
* This interface does not enforce any particular timer resolution, so
+
* special care may have to be taken if you rely on very high precision with
+
* millisecond accuracy or below. Event loop implementations SHOULD work on
+
* a best effort basis and SHOULD provide at least millisecond accuracy
+
* unless otherwise noted. Many existing event loop implementations are
+
* known to provide microsecond accuracy, but it's generally not recommended
+
* to rely on this high precision.
+
*
+
* Similarly, the execution order of timers scheduled to execute at the
+
* same time (within its possible accuracy) is not guaranteed.
+
*
+
* This interface suggests that event loop implementations SHOULD use a
+
* monotonic time source if available. Given that a monotonic time source is
+
* only available as of PHP 7.3 by default, event loop implementations MAY
+
* fall back to using wall-clock time.
+
* While this does not affect many common use cases, this is an important
+
* distinction for programs that rely on a high time precision or on systems
+
* that are subject to discontinuous time adjustments (time jumps).
+
* This means that if you schedule a timer to trigger in 30s and then adjust
+
* your system time forward by 20s, the timer SHOULD still trigger in 30s.
+
* See also [event loop implementations](#loop-implementations) for more details.
+
*
+
* Additionally, periodic timers may be subject to timer drift due to
+
* re-scheduling after each invocation. As such, it's generally not
+
* recommended to rely on this for high precision intervals with millisecond
+
* accuracy or below.
+
*
+
* @param int|float $interval The number of seconds to wait before execution.
+
* @param callable $callback The callback to invoke.
+
*
+
* @return TimerInterface
+
*/
+
public function addPeriodicTimer($interval, $callback);
+
+
/**
+
* Cancel a pending timer.
+
*
+
* See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples).
+
*
+
* Calling this method on a timer instance that has not been added to this
+
* loop instance or on a timer that has already been cancelled has no effect.
+
*
+
* @param TimerInterface $timer The timer to cancel.
+
*
+
* @return void
+
*/
+
public function cancelTimer(TimerInterface $timer);
+
+
/**
+
* Schedule a callback to be invoked on a future tick of the event loop.
+
*
+
* This works very much similar to timers with an interval of zero seconds,
+
* but does not require the overhead of scheduling a timer queue.
+
*
+
* The tick callback function MUST be able to accept zero parameters.
+
*
+
* The tick callback function MUST NOT throw an `Exception`.
+
* The return value of the tick callback function will be ignored and has
+
* no effect, so for performance reasons you're recommended to not return
+
* any excessive data structures.
+
*
+
* If you want to access any variables within your callback function, you
+
* can bind arbitrary data to a callback closure like this:
+
*
+
* ```php
+
* function hello($name, LoopInterface $loop)
+
* {
+
* $loop->futureTick(function () use ($name) {
+
* echo "hello $name\n";
+
* });
+
* }
+
*
+
* hello('Tester', $loop);
+
* ```
+
*
+
* Unlike timers, tick callbacks are guaranteed to be executed in the order
+
* they are enqueued.
+
* Also, once a callback is enqueued, there's no way to cancel this operation.
+
*
+
* This is often used to break down bigger tasks into smaller steps (a form
+
* of cooperative multitasking).
+
*
+
* ```php
+
* $loop->futureTick(function () {
+
* echo 'b';
+
* });
+
* $loop->futureTick(function () {
+
* echo 'c';
+
* });
+
* echo 'a';
+
* ```
+
*
+
* See also [example #3](examples).
+
*
+
* @param callable $listener The callback to invoke.
+
*
+
* @return void
+
*/
+
public function futureTick($listener);
+
+
/**
+
* Register a listener to be notified when a signal has been caught by this process.
+
*
+
* This is useful to catch user interrupt signals or shutdown signals from
+
* tools like `supervisor` or `systemd`.
+
*
+
* The second parameter MUST be a listener callback function that accepts
+
* the signal as its only parameter.
+
* If you don't use the signal inside your listener callback function
+
* you MAY use a function which has no parameters at all.
+
*
+
* The listener callback function MUST NOT throw an `Exception`.
+
* The return value of the listener callback function will be ignored and has
+
* no effect, so for performance reasons you're recommended to not return
+
* any excessive data structures.
+
*
+
* ```php
+
* $loop->addSignal(SIGINT, function (int $signal) {
+
* echo 'Caught user interrupt signal' . PHP_EOL;
+
* });
+
* ```
+
*
+
* See also [example #4](examples).
+
*
+
* Signaling is only available on Unix-like platforms, Windows isn't
+
* supported due to operating system limitations.
+
* This method may throw a `BadMethodCallException` if signals aren't
+
* supported on this platform, for example when required extensions are
+
* missing.
+
*
+
* **Note: A listener can only be added once to the same signal, any
+
* attempts to add it more than once will be ignored.**
+
*
+
* @param int $signal
+
* @param callable $listener
+
*
+
* @throws \BadMethodCallException when signals aren't supported on this
+
* platform, for example when required extensions are missing.
+
*
+
* @return void
+
*/
+
public function addSignal($signal, $listener);
+
+
/**
+
* Removes a previously added signal listener.
+
*
+
* ```php
+
* $loop->removeSignal(SIGINT, $listener);
+
* ```
+
*
+
* Any attempts to remove listeners that aren't registered will be ignored.
+
*
+
* @param int $signal
+
* @param callable $listener
+
*
+
* @return void
+
*/
+
public function removeSignal($signal, $listener);
+
+
/**
+
* Run the event loop until there are no more tasks to perform.
+
*
+
* For many applications, this method is the only directly visible
+
* invocation on the event loop.
+
* As a rule of thumb, it is usually recommended to attach everything to the
+
* same loop instance and then run the loop once at the bottom end of the
+
* application.
+
*
+
* ```php
+
* $loop->run();
+
* ```
+
*
+
* This method will keep the loop running until there are no more tasks
+
* to perform. In other words: This method will block until the last
+
* timer, stream and/or signal has been removed.
+
*
+
* Likewise, it is imperative to ensure the application actually invokes
+
* this method once. Adding listeners to the loop and missing to actually
+
* run it will result in the application exiting without actually waiting
+
* for any of the attached listeners.
+
*
+
* This method MUST NOT be called while the loop is already running.
+
* This method MAY be called more than once after it has explicitly been
+
* [`stop()`ped](#stop) or after it automatically stopped because it
+
* previously did no longer have anything to do.
+
*
+
* @return void
+
*/
+
public function run();
+
+
/**
+
* Instruct a running event loop to stop.
+
*
+
* This method is considered advanced usage and should be used with care.
+
* As a rule of thumb, it is usually recommended to let the loop stop
+
* only automatically when it no longer has anything to do.
+
*
+
* This method can be used to explicitly instruct the event loop to stop:
+
*
+
* ```php
+
* $loop->addTimer(3.0, function () use ($loop) {
+
* $loop->stop();
+
* });
+
* ```
+
*
+
* Calling this method on a loop instance that is not currently running or
+
* on a loop instance that has already been stopped has no effect.
+
*
+
* @return void
+
*/
+
public function stop();
+
}
+63
vendor/react/event-loop/src/SignalsHandler.php
···
+
<?php
+
+
namespace React\EventLoop;
+
+
/**
+
* @internal
+
*/
+
final class SignalsHandler
+
{
+
private $signals = array();
+
+
public function add($signal, $listener)
+
{
+
if (!isset($this->signals[$signal])) {
+
$this->signals[$signal] = array();
+
}
+
+
if (\in_array($listener, $this->signals[$signal])) {
+
return;
+
}
+
+
$this->signals[$signal][] = $listener;
+
}
+
+
public function remove($signal, $listener)
+
{
+
if (!isset($this->signals[$signal])) {
+
return;
+
}
+
+
$index = \array_search($listener, $this->signals[$signal], true);
+
unset($this->signals[$signal][$index]);
+
+
if (isset($this->signals[$signal]) && \count($this->signals[$signal]) === 0) {
+
unset($this->signals[$signal]);
+
}
+
}
+
+
public function call($signal)
+
{
+
if (!isset($this->signals[$signal])) {
+
return;
+
}
+
+
foreach ($this->signals[$signal] as $listener) {
+
\call_user_func($listener, $signal);
+
}
+
}
+
+
public function count($signal)
+
{
+
if (!isset($this->signals[$signal])) {
+
return 0;
+
}
+
+
return \count($this->signals[$signal]);
+
}
+
+
public function isEmpty()
+
{
+
return !$this->signals;
+
}
+
}
+330
vendor/react/event-loop/src/StreamSelectLoop.php
···
+
<?php
+
+
namespace React\EventLoop;
+
+
use React\EventLoop\Tick\FutureTickQueue;
+
use React\EventLoop\Timer\Timer;
+
use React\EventLoop\Timer\Timers;
+
+
/**
+
* A `stream_select()` based event loop.
+
*
+
* This uses the [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
+
* function and is the only implementation that works out of the box with PHP.
+
*
+
* This event loop works out of the box on PHP 5.4 through PHP 8+ and HHVM.
+
* This means that no installation is required and this library works on all
+
* platforms and supported PHP versions.
+
* Accordingly, the [`Loop` class](#loop) and the deprecated [`Factory`](#factory)
+
* will use this event loop by default if you do not install any of the event loop
+
* extensions listed below.
+
*
+
* Under the hood, it does a simple `select` system call.
+
* This system call is limited to the maximum file descriptor number of
+
* `FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)`
+
* (`m` being the maximum file descriptor number passed).
+
* This means that you may run into issues when handling thousands of streams
+
* concurrently and you may want to look into using one of the alternative
+
* event loop implementations listed below in this case.
+
* If your use case is among the many common use cases that involve handling only
+
* dozens or a few hundred streams at once, then this event loop implementation
+
* performs really well.
+
*
+
* If you want to use signal handling (see also [`addSignal()`](#addsignal) below),
+
* this event loop implementation requires `ext-pcntl`.
+
* This extension is only available for Unix-like platforms and does not support
+
* Windows.
+
* It is commonly installed as part of many PHP distributions.
+
* If this extension is missing (or you're running on Windows), signal handling is
+
* not supported and throws a `BadMethodCallException` instead.
+
*
+
* This event loop is known to rely on wall-clock time to schedule future timers
+
* when using any version before PHP 7.3, because a monotonic time source is
+
* only available as of PHP 7.3 (`hrtime()`).
+
* While this does not affect many common use cases, this is an important
+
* distinction for programs that rely on a high time precision or on systems
+
* that are subject to discontinuous time adjustments (time jumps).
+
* This means that if you schedule a timer to trigger in 30s on PHP < 7.3 and
+
* then adjust your system time forward by 20s, the timer may trigger in 10s.
+
* See also [`addTimer()`](#addtimer) for more details.
+
*
+
* @link https://www.php.net/manual/en/function.stream-select.php
+
*/
+
final class StreamSelectLoop implements LoopInterface
+
{
+
/** @internal */
+
const MICROSECONDS_PER_SECOND = 1000000;
+
+
private $futureTickQueue;
+
private $timers;
+
private $readStreams = array();
+
private $readListeners = array();
+
private $writeStreams = array();
+
private $writeListeners = array();
+
private $running;
+
private $pcntl = false;
+
private $pcntlPoll = false;
+
private $signals;
+
+
public function __construct()
+
{
+
$this->futureTickQueue = new FutureTickQueue();
+
$this->timers = new Timers();
+
$this->pcntl = \function_exists('pcntl_signal') && \function_exists('pcntl_signal_dispatch');
+
$this->pcntlPoll = $this->pcntl && !\function_exists('pcntl_async_signals');
+
$this->signals = new SignalsHandler();
+
+
// prefer async signals if available (PHP 7.1+) or fall back to dispatching on each tick
+
if ($this->pcntl && !$this->pcntlPoll) {
+
\pcntl_async_signals(true);
+
}
+
}
+
+
public function addReadStream($stream, $listener)
+
{
+
$key = (int) $stream;
+
+
if (!isset($this->readStreams[$key])) {
+
$this->readStreams[$key] = $stream;
+
$this->readListeners[$key] = $listener;
+
}
+
}
+
+
public function addWriteStream($stream, $listener)
+
{
+
$key = (int) $stream;
+
+
if (!isset($this->writeStreams[$key])) {
+
$this->writeStreams[$key] = $stream;
+
$this->writeListeners[$key] = $listener;
+
}
+
}
+
+
public function removeReadStream($stream)
+
{
+
$key = (int) $stream;
+
+
unset(
+
$this->readStreams[$key],
+
$this->readListeners[$key]
+
);
+
}
+
+
public function removeWriteStream($stream)
+
{
+
$key = (int) $stream;
+
+
unset(
+
$this->writeStreams[$key],
+
$this->writeListeners[$key]
+
);
+
}
+
+
public function addTimer($interval, $callback)
+
{
+
$timer = new Timer($interval, $callback, false);
+
+
$this->timers->add($timer);
+
+
return $timer;
+
}
+
+
public function addPeriodicTimer($interval, $callback)
+
{
+
$timer = new Timer($interval, $callback, true);
+
+
$this->timers->add($timer);
+
+
return $timer;
+
}
+
+
public function cancelTimer(TimerInterface $timer)
+
{
+
$this->timers->cancel($timer);
+
}
+
+
public function futureTick($listener)
+
{
+
$this->futureTickQueue->add($listener);
+
}
+
+
public function addSignal($signal, $listener)
+
{
+
if ($this->pcntl === false) {
+
throw new \BadMethodCallException('Event loop feature "signals" isn\'t supported by the "StreamSelectLoop"');
+
}
+
+
$first = $this->signals->count($signal) === 0;
+
$this->signals->add($signal, $listener);
+
+
if ($first) {
+
\pcntl_signal($signal, array($this->signals, 'call'));
+
}
+
}
+
+
public function removeSignal($signal, $listener)
+
{
+
if (!$this->signals->count($signal)) {
+
return;
+
}
+
+
$this->signals->remove($signal, $listener);
+
+
if ($this->signals->count($signal) === 0) {
+
\pcntl_signal($signal, \SIG_DFL);
+
}
+
}
+
+
public function run()
+
{
+
$this->running = true;
+
+
while ($this->running) {
+
$this->futureTickQueue->tick();
+
+
$this->timers->tick();
+
+
// Future-tick queue has pending callbacks ...
+
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
+
$timeout = 0;
+
+
// There is a pending timer, only block until it is due ...
+
} elseif ($scheduledAt = $this->timers->getFirst()) {
+
$timeout = $scheduledAt - $this->timers->getTime();
+
if ($timeout < 0) {
+
$timeout = 0;
+
} else {
+
// Convert float seconds to int microseconds.
+
// Ensure we do not exceed maximum integer size, which may
+
// cause the loop to tick once every ~35min on 32bit systems.
+
$timeout *= self::MICROSECONDS_PER_SECOND;
+
$timeout = $timeout > \PHP_INT_MAX ? \PHP_INT_MAX : (int)$timeout;
+
}
+
+
// The only possible event is stream or signal activity, so wait forever ...
+
} elseif ($this->readStreams || $this->writeStreams || !$this->signals->isEmpty()) {
+
$timeout = null;
+
+
// There's nothing left to do ...
+
} else {
+
break;
+
}
+
+
$this->waitForStreamActivity($timeout);
+
}
+
}
+
+
public function stop()
+
{
+
$this->running = false;
+
}
+
+
/**
+
* Wait/check for stream activity, or until the next timer is due.
+
*
+
* @param integer|null $timeout Activity timeout in microseconds, or null to wait forever.
+
*/
+
private function waitForStreamActivity($timeout)
+
{
+
$read = $this->readStreams;
+
$write = $this->writeStreams;
+
+
$available = $this->streamSelect($read, $write, $timeout);
+
if ($this->pcntlPoll) {
+
\pcntl_signal_dispatch();
+
}
+
if (false === $available) {
+
// if a system call has been interrupted,
+
// we cannot rely on it's outcome
+
return;
+
}
+
+
foreach ($read as $stream) {
+
$key = (int) $stream;
+
+
if (isset($this->readListeners[$key])) {
+
\call_user_func($this->readListeners[$key], $stream);
+
}
+
}
+
+
foreach ($write as $stream) {
+
$key = (int) $stream;
+
+
if (isset($this->writeListeners[$key])) {
+
\call_user_func($this->writeListeners[$key], $stream);
+
}
+
}
+
}
+
+
/**
+
* Emulate a stream_select() implementation that does not break when passed
+
* empty stream arrays.
+
*
+
* @param array $read An array of read streams to select upon.
+
* @param array $write An array of write streams to select upon.
+
* @param int|null $timeout Activity timeout in microseconds, or null to wait forever.
+
*
+
* @return int|false The total number of streams that are ready for read/write.
+
* Can return false if stream_select() is interrupted by a signal.
+
*/
+
private function streamSelect(array &$read, array &$write, $timeout)
+
{
+
if ($read || $write) {
+
// We do not usually use or expose the `exceptfds` parameter passed to the underlying `select`.
+
// However, Windows does not report failed connection attempts in `writefds` passed to `select` like most other platforms.
+
// Instead, it uses `writefds` only for successful connection attempts and `exceptfds` for failed connection attempts.
+
// We work around this by adding all sockets that look like a pending connection attempt to `exceptfds` automatically on Windows and merge it back later.
+
// This ensures the public API matches other loop implementations across all platforms (see also test suite or rather test matrix).
+
// Lacking better APIs, every write-only socket that has not yet read any data is assumed to be in a pending connection attempt state.
+
// @link https://docs.microsoft.com/de-de/windows/win32/api/winsock2/nf-winsock2-select
+
$except = null;
+
if (\DIRECTORY_SEPARATOR === '\\') {
+
$except = array();
+
foreach ($write as $key => $socket) {
+
if (!isset($read[$key]) && @\ftell($socket) === 0) {
+
$except[$key] = $socket;
+
}
+
}
+
}
+
+
/** @var ?callable $previous */
+
$previous = \set_error_handler(function ($errno, $errstr) use (&$previous) {
+
// suppress warnings that occur when `stream_select()` is interrupted by a signal
+
// PHP defines `EINTR` through `ext-sockets` or `ext-pcntl`, otherwise use common default (Linux & Mac)
+
$eintr = \defined('SOCKET_EINTR') ? \SOCKET_EINTR : (\defined('PCNTL_EINTR') ? \PCNTL_EINTR : 4);
+
if ($errno === \E_WARNING && \strpos($errstr, '[' . $eintr .']: ') !== false) {
+
return;
+
}
+
+
// forward any other error to registered error handler or print warning
+
return ($previous !== null) ? \call_user_func_array($previous, \func_get_args()) : false;
+
});
+
+
try {
+
$ret = \stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
+
\restore_error_handler();
+
} catch (\Throwable $e) { // @codeCoverageIgnoreStart
+
\restore_error_handler();
+
throw $e;
+
} catch (\Exception $e) {
+
\restore_error_handler();
+
throw $e;
+
} // @codeCoverageIgnoreEnd
+
+
if ($except) {
+
$write = \array_merge($write, $except);
+
}
+
return $ret;
+
}
+
+
if ($timeout > 0) {
+
\usleep($timeout);
+
} elseif ($timeout === null) {
+
// wait forever (we only reach this if we're only awaiting signals)
+
// this may be interrupted and return earlier when a signal is received
+
\sleep(PHP_INT_MAX);
+
}
+
+
return 0;
+
}
+
}
+60
vendor/react/event-loop/src/Tick/FutureTickQueue.php
···
+
<?php
+
+
namespace React\EventLoop\Tick;
+
+
use SplQueue;
+
+
/**
+
* A tick queue implementation that can hold multiple callback functions
+
*
+
* This class should only be used internally, see LoopInterface instead.
+
*
+
* @see LoopInterface
+
* @internal
+
*/
+
final class FutureTickQueue
+
{
+
private $queue;
+
+
public function __construct()
+
{
+
$this->queue = new SplQueue();
+
}
+
+
/**
+
* Add a callback to be invoked on a future tick of the event loop.
+
*
+
* Callbacks are guaranteed to be executed in the order they are enqueued.
+
*
+
* @param callable $listener The callback to invoke.
+
*/
+
public function add($listener)
+
{
+
$this->queue->enqueue($listener);
+
}
+
+
/**
+
* Flush the callback queue.
+
*/
+
public function tick()
+
{
+
// Only invoke as many callbacks as were on the queue when tick() was called.
+
$count = $this->queue->count();
+
+
while ($count--) {
+
\call_user_func(
+
$this->queue->dequeue()
+
);
+
}
+
}
+
+
/**
+
* Check if the next tick queue is empty.
+
*
+
* @return boolean
+
*/
+
public function isEmpty()
+
{
+
return $this->queue->isEmpty();
+
}
+
}
+55
vendor/react/event-loop/src/Timer/Timer.php
···
+
<?php
+
+
namespace React\EventLoop\Timer;
+
+
use React\EventLoop\TimerInterface;
+
+
/**
+
* The actual connection implementation for TimerInterface
+
*
+
* This class should only be used internally, see TimerInterface instead.
+
*
+
* @see TimerInterface
+
* @internal
+
*/
+
final class Timer implements TimerInterface
+
{
+
const MIN_INTERVAL = 0.000001;
+
+
private $interval;
+
private $callback;
+
private $periodic;
+
+
/**
+
* Constructor initializes the fields of the Timer
+
*
+
* @param float $interval The interval after which this timer will execute, in seconds
+
* @param callable $callback The callback that will be executed when this timer elapses
+
* @param bool $periodic Whether the time is periodic
+
*/
+
public function __construct($interval, $callback, $periodic = false)
+
{
+
if ($interval < self::MIN_INTERVAL) {
+
$interval = self::MIN_INTERVAL;
+
}
+
+
$this->interval = (float) $interval;
+
$this->callback = $callback;
+
$this->periodic = (bool) $periodic;
+
}
+
+
public function getInterval()
+
{
+
return $this->interval;
+
}
+
+
public function getCallback()
+
{
+
return $this->callback;
+
}
+
+
public function isPeriodic()
+
{
+
return $this->periodic;
+
}
+
}
+113
vendor/react/event-loop/src/Timer/Timers.php
···
+
<?php
+
+
namespace React\EventLoop\Timer;
+
+
use React\EventLoop\TimerInterface;
+
+
/**
+
* A scheduler implementation that can hold multiple timer instances
+
*
+
* This class should only be used internally, see TimerInterface instead.
+
*
+
* @see TimerInterface
+
* @internal
+
*/
+
final class Timers
+
{
+
private $time;
+
private $timers = array();
+
private $schedule = array();
+
private $sorted = true;
+
private $useHighResolution;
+
+
public function __construct()
+
{
+
// prefer high-resolution timer, available as of PHP 7.3+
+
$this->useHighResolution = \function_exists('hrtime');
+
}
+
+
public function updateTime()
+
{
+
return $this->time = $this->useHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
+
}
+
+
public function getTime()
+
{
+
return $this->time ?: $this->updateTime();
+
}
+
+
public function add(TimerInterface $timer)
+
{
+
$id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer);
+
$this->timers[$id] = $timer;
+
$this->schedule[$id] = $timer->getInterval() + $this->updateTime();
+
$this->sorted = false;
+
}
+
+
public function contains(TimerInterface $timer)
+
{
+
$id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer);
+
return isset($this->timers[$id]);
+
}
+
+
public function cancel(TimerInterface $timer)
+
{
+
$id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer);
+
unset($this->timers[$id], $this->schedule[$id]);
+
}
+
+
public function getFirst()
+
{
+
// ensure timers are sorted to simply accessing next (first) one
+
if (!$this->sorted) {
+
$this->sorted = true;
+
\asort($this->schedule);
+
}
+
+
return \reset($this->schedule);
+
}
+
+
public function isEmpty()
+
{
+
return \count($this->timers) === 0;
+
}
+
+
public function tick()
+
{
+
// hot path: skip timers if nothing is scheduled
+
if (!$this->schedule) {
+
return;
+
}
+
+
// ensure timers are sorted so we can execute in order
+
if (!$this->sorted) {
+
$this->sorted = true;
+
\asort($this->schedule);
+
}
+
+
$time = $this->updateTime();
+
+
foreach ($this->schedule as $id => $scheduled) {
+
// schedule is ordered, so loop until first timer that is not scheduled for execution now
+
if ($scheduled >= $time) {
+
break;
+
}
+
+
// skip any timers that are removed while we process the current schedule
+
if (!isset($this->schedule[$id]) || $this->schedule[$id] !== $scheduled) {
+
continue;
+
}
+
+
$timer = $this->timers[$id];
+
\call_user_func($timer->getCallback(), $timer);
+
+
// re-schedule if this is a periodic timer and it has not been cancelled explicitly already
+
if ($timer->isPeriodic() && isset($this->timers[$id])) {
+
$this->schedule[$id] = $timer->getInterval() + $time;
+
$this->sorted = false;
+
} else {
+
unset($this->timers[$id], $this->schedule[$id]);
+
}
+
}
+
}
+
}
+27
vendor/react/event-loop/src/TimerInterface.php
···
+
<?php
+
+
namespace React\EventLoop;
+
+
interface TimerInterface
+
{
+
/**
+
* Get the interval after which this timer will execute, in seconds
+
*
+
* @return float
+
*/
+
public function getInterval();
+
+
/**
+
* Get the callback that will be executed when this timer elapses
+
*
+
* @return callable
+
*/
+
public function getCallback();
+
+
/**
+
* Determine whether the time is periodic
+
*
+
* @return bool
+
*/
+
public function isPeriodic();
+
}
+156
vendor/react/promise/CHANGELOG.md
···
+
# Changelog
+
+
## 3.2.0 (2024-05-24)
+
+
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
+
(#260 by @Ayesh)
+
+
* Feature: Include previous exceptions when reporting unhandled promise rejections.
+
(#262 by @clue)
+
+
* Update test suite to improve PHP 8.4+ support.
+
(#261 by @SimonFrings)
+
+
## 3.1.0 (2023-11-16)
+
+
* Feature: Full PHP 8.3 compatibility.
+
(#255 by @clue)
+
+
* Feature: Describe all callable arguments with types for `Promise` and `Deferred`.
+
(#253 by @clue)
+
+
* Update test suite and minor documentation improvements.
+
(#251 by @ondrejmirtes and #250 by @SQKo)
+
+
## 3.0.0 (2023-07-11)
+
+
A major new feature release, see [**release announcement**](https://clue.engineering/2023/announcing-reactphp-promise-v3).
+
+
* We'd like to emphasize that this component is production ready and battle-tested.
+
We plan to support all long-term support (LTS) releases for at least 24 months,
+
so you have a rock-solid foundation to build on top of.
+
+
* The v3 release will be the way forward for this package. However, we will still
+
actively support v2 and v1 to provide a smooth upgrade path for those not yet
+
on the latest versions.
+
+
This update involves some major new features and a minor BC break over the
+
`v2.0.0` release. We've tried hard to avoid BC breaks where possible and
+
minimize impact otherwise. We expect that most consumers of this package will be
+
affected by BC breaks, but updating should take no longer than a few minutes.
+
See below for more details:
+
+
* BC break: PHP 8.1+ recommended, PHP 7.1+ required.
+
(#138 and #149 by @WyriHaximus)
+
+
* Feature / BC break: The `PromiseInterface` now includes the functionality of the old ~~`ExtendedPromiseInterface`~~ and ~~`CancellablePromiseInterface`~~.
+
Each promise now always includes the `then()`, `catch()`, `finally()` and `cancel()` methods.
+
The new `catch()` and `finally()` methods replace the deprecated ~~`otherwise()`~~ and ~~`always()`~~ methods which continue to exist for BC reasons.
+
The old ~~`ExtendedPromiseInterface`~~ and ~~`CancellablePromiseInterface`~~ are no longer needed and have been removed as a consequence.
+
(#75 by @jsor and #208 by @clue and @WyriHaximus)
+
+
```php
+
// old (multiple interfaces may or may not be implemented)
+
assert($promise instanceof PromiseInterface);
+
assert(method_exists($promise, 'then'));
+
if ($promise instanceof ExtendedPromiseInterface) { assert(method_exists($promise, 'otherwise')); }
+
if ($promise instanceof ExtendedPromiseInterface) { assert(method_exists($promise, 'always')); }
+
if ($promise instanceof CancellablePromiseInterface) { assert(method_exists($promise, 'cancel')); }
+
+
// new (single PromiseInterface with all methods)
+
assert($promise instanceof PromiseInterface);
+
assert(method_exists($promise, 'then'));
+
assert(method_exists($promise, 'catch'));
+
assert(method_exists($promise, 'finally'));
+
assert(method_exists($promise, 'cancel'));
+
```
+
+
* Feature / BC break: Improve type safety of promises. Require `mixed` fulfillment value argument and `Throwable` (or `Exception`) as rejection reason.
+
Add PHPStan template types to ensure strict types for `resolve(T $value): PromiseInterface<T>` and `reject(Throwable $reason): PromiseInterface<never>`.
+
It is no longer possible to resolve a promise without a value (use `null` instead) or reject a promise without a reason (use `Throwable` instead).
+
(#93, #141 and #142 by @jsor, #138, #149 and #247 by @WyriHaximus and #213 and #246 by @clue)
+
+
```php
+
// old (arguments used to be optional)
+
$promise = resolve();
+
$promise = reject();
+
+
// new (already supported before)
+
$promise = resolve(null);
+
$promise = reject(new RuntimeException());
+
```
+
+
* Feature / BC break: Report all unhandled rejections by default and remove ~~`done()`~~ method.
+
Add new `set_rejection_handler()` function to set the global rejection handler for unhandled promise rejections.
+
(#248, #249 and #224 by @clue)
+
+
```php
+
// Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
+
reject(new RuntimeException('Unhandled'));
+
```
+
+
* BC break: Remove all deprecated APIs and reduce API surface.
+
Remove ~~`some()`~~, ~~`map()`~~, ~~`reduce()`~~ functions, use `any()` and `all()` functions instead.
+
Remove internal ~~`FulfilledPromise`~~ and ~~`RejectedPromise`~~ classes, use `resolve()` and `reject()` functions instead.
+
Remove legacy promise progress API (deprecated third argument to `then()` method) and deprecated ~~`LazyPromise`~~ class.
+
(#32 and #98 by @jsor and #164, #219 and #220 by @clue)
+
+
* BC break: Make all classes final to encourage composition over inheritance.
+
(#80 by @jsor)
+
+
* Feature / BC break: Require `array` (or `iterable`) type for `all()` + `race()` + `any()` functions and bring in line with ES6 specification.
+
These functions now require a single argument with a variable number of promises or values as input.
+
(#225 by @clue and #35 by @jsor)
+
+
* Fix / BC break: Fix `race()` to return a forever pending promise when called with an empty `array` (or `iterable`) and bring in line with ES6 specification.
+
(#83 by @jsor and #225 by @clue)
+
+
* Minor performance improvements by initializing `Deferred` in the constructor and avoiding `call_user_func()` calls.
+
(#151 by @WyriHaximus and #171 by @Kubo2)
+
+
* Minor documentation improvements.
+
(#110 by @seregazhuk, #132 by @CharlotteDunois, #145 by @danielecr, #178 by @WyriHaximus, #189 by @srdante, #212 by @clue, #214, #239 and #243 by @SimonFrings and #231 by @nhedger)
+
+
The following changes had to be ported to this release due to our branching
+
strategy, but also appeared in the [`2.x` branch](https://github.com/reactphp/promise/tree/2.x):
+
+
* Feature: Support union types and address deprecation of `ReflectionType::getClass()` (PHP 8+).
+
(#197 by @cdosoftei and @SimonFrings)
+
+
* Feature: Support intersection types (PHP 8.1+).
+
(#209 by @bzikarsky)
+
+
* Feature: Support DNS types (PHP 8.2+).
+
(#236 by @nhedger)
+
+
* Feature: Port all memory improvements from `2.x` to `3.x`.
+
(#150 by @clue and @WyriHaximus)
+
+
* Fix: Fix checking whether cancellable promise is an object and avoid possible warning.
+
(#161 by @smscr)
+
+
* Improve performance by prefixing all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
+
(#134 by @WyriHaximus)
+
+
* Improve test suite, update PHPUnit and PHP versions and add `.gitattributes` to exclude dev files from exports.
+
(#107 by @carusogabriel, #148 and #234 by @WyriHaximus, #153 by @reedy, #162, #230 and #240 by @clue, #173, #177, #185 and #199 by @SimonFrings, #193 by @woodongwong and #210 by @bzikarsky)
+
+
The following changes were originally planned for this release but later reverted
+
and are not part of the final release:
+
+
* Add iterative callback queue handler to avoid recursion (later removed to improve Fiber support).
+
(#28, #82 and #86 by @jsor, #158 by @WyriHaximus and #229 and #238 by @clue)
+
+
* Trigger an `E_USER_ERROR` instead of throwing an exception from `done()` (later removed entire `done()` method to globally report unhandled rejections).
+
(#97 by @jsor and #224 and #248 by @clue)
+
+
* Add type declarations for `some()` (later removed entire `some()` function).
+
(#172 by @WyriHaximus and #219 by @clue)
+
+
## 2.0.0 (2013-12-10)
+
+
See [`2.x` CHANGELOG](https://github.com/reactphp/promise/blob/2.x/CHANGELOG.md) for more details.
+
+
## 1.0.0 (2012-11-07)
+
+
See [`1.x` CHANGELOG](https://github.com/reactphp/promise/blob/1.x/CHANGELOG.md) for more details.
+24
vendor/react/promise/LICENSE
···
+
The MIT License (MIT)
+
+
Copyright (c) 2012 Jan Sorgalla, Christian Lück, Cees-Jan Kiewiet, Chris Boden
+
+
Permission is hereby granted, free of charge, to any person
+
obtaining a copy of this software and associated documentation
+
files (the "Software"), to deal in the Software without
+
restriction, including without limitation the rights to use,
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
+
copies of the Software, and to permit persons to whom the
+
Software is furnished to do so, subject to the following
+
conditions:
+
+
The above copyright notice and this permission notice shall be
+
included in all copies or substantial portions of the Software.
+
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+
OTHER DEALINGS IN THE SOFTWARE.
+722
vendor/react/promise/README.md
···
+
Promise
+
=======
+
+
A lightweight implementation of
+
[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
+
+
[![CI status](https://github.com/reactphp/promise/workflows/CI/badge.svg)](https://github.com/reactphp/promise/actions)
+
[![installs on Packagist](https://img.shields.io/packagist/dt/react/promise?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/promise)
+
+
Table of Contents
+
-----------------
+
+
1. [Introduction](#introduction)
+
2. [Concepts](#concepts)
+
* [Deferred](#deferred)
+
* [Promise](#promise-1)
+
3. [API](#api)
+
* [Deferred](#deferred-1)
+
* [Deferred::promise()](#deferredpromise)
+
* [Deferred::resolve()](#deferredresolve)
+
* [Deferred::reject()](#deferredreject)
+
* [PromiseInterface](#promiseinterface)
+
* [PromiseInterface::then()](#promiseinterfacethen)
+
* [PromiseInterface::catch()](#promiseinterfacecatch)
+
* [PromiseInterface::finally()](#promiseinterfacefinally)
+
* [PromiseInterface::cancel()](#promiseinterfacecancel)
+
* [~~PromiseInterface::otherwise()~~](#promiseinterfaceotherwise)
+
* [~~PromiseInterface::always()~~](#promiseinterfacealways)
+
* [Promise](#promise-2)
+
* [Functions](#functions)
+
* [resolve()](#resolve)
+
* [reject()](#reject)
+
* [all()](#all)
+
* [race()](#race)
+
* [any()](#any)
+
* [set_rejection_handler()](#set_rejection_handler)
+
4. [Examples](#examples)
+
* [How to use Deferred](#how-to-use-deferred)
+
* [How promise forwarding works](#how-promise-forwarding-works)
+
* [Resolution forwarding](#resolution-forwarding)
+
* [Rejection forwarding](#rejection-forwarding)
+
* [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding)
+
5. [Install](#install)
+
6. [Tests](#tests)
+
7. [Credits](#credits)
+
8. [License](#license)
+
+
Introduction
+
------------
+
+
Promise is a library implementing
+
[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
+
+
It also provides several other useful promise-related concepts, such as joining
+
multiple promises and mapping and reducing collections of promises.
+
+
If you've never heard about promises before,
+
[read this first](https://gist.github.com/domenic/3889970).
+
+
Concepts
+
--------
+
+
### Deferred
+
+
A **Deferred** represents a computation or unit of work that may not have
+
completed yet. Typically (but not always), that computation will be something
+
that executes asynchronously and completes at some point in the future.
+
+
### Promise
+
+
While a deferred represents the computation itself, a **Promise** represents
+
the result of that computation. Thus, each deferred has a promise that acts as
+
a placeholder for its actual result.
+
+
API
+
---
+
+
### Deferred
+
+
A deferred represents an operation whose resolution is pending. It has separate
+
promise and resolver parts.
+
+
```php
+
$deferred = new React\Promise\Deferred();
+
+
$promise = $deferred->promise();
+
+
$deferred->resolve(mixed $value);
+
$deferred->reject(\Throwable $reason);
+
```
+
+
The `promise` method returns the promise of the deferred.
+
+
The `resolve` and `reject` methods control the state of the deferred.
+
+
The constructor of the `Deferred` accepts an optional `$canceller` argument.
+
See [Promise](#promise-2) for more information.
+
+
#### Deferred::promise()
+
+
```php
+
$promise = $deferred->promise();
+
```
+
+
Returns the promise of the deferred, which you can hand out to others while
+
keeping the authority to modify its state to yourself.
+
+
#### Deferred::resolve()
+
+
```php
+
$deferred->resolve(mixed $value);
+
```
+
+
Resolves the promise returned by `promise()`. All consumers are notified by
+
having `$onFulfilled` (which they registered via `$promise->then()`) called with
+
`$value`.
+
+
If `$value` itself is a promise, the promise will transition to the state of
+
this promise once it is resolved.
+
+
See also the [`resolve()` function](#resolve).
+
+
#### Deferred::reject()
+
+
```php
+
$deferred->reject(\Throwable $reason);
+
```
+
+
Rejects the promise returned by `promise()`, signalling that the deferred's
+
computation failed.
+
All consumers are notified by having `$onRejected` (which they registered via
+
`$promise->then()`) called with `$reason`.
+
+
See also the [`reject()` function](#reject).
+
+
### PromiseInterface
+
+
The promise interface provides the common interface for all promise
+
implementations.
+
See [Promise](#promise-2) for the only public implementation exposed by this
+
package.
+
+
A promise represents an eventual outcome, which is either fulfillment (success)
+
and an associated value, or rejection (failure) and an associated reason.
+
+
Once in the fulfilled or rejected state, a promise becomes immutable.
+
Neither its state nor its result (or error) can be modified.
+
+
#### PromiseInterface::then()
+
+
```php
+
$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null);
+
```
+
+
Transforms a promise's value by applying a function to the promise's fulfillment
+
or rejection value. Returns a new promise for the transformed result.
+
+
The `then()` method registers new fulfilled and rejection handlers with a promise
+
(all parameters are optional):
+
+
* `$onFulfilled` will be invoked once the promise is fulfilled and passed
+
the result as the first argument.
+
* `$onRejected` will be invoked once the promise is rejected and passed the
+
reason as the first argument.
+
+
It returns a new promise that will fulfill with the return value of either
+
`$onFulfilled` or `$onRejected`, whichever is called, or will reject with
+
the thrown exception if either throws.
+
+
A promise makes the following guarantees about handlers registered in
+
the same call to `then()`:
+
+
1. Only one of `$onFulfilled` or `$onRejected` will be called,
+
never both.
+
2. `$onFulfilled` and `$onRejected` will never be called more
+
than once.
+
+
#### See also
+
+
* [resolve()](#resolve) - Creating a resolved promise
+
* [reject()](#reject) - Creating a rejected promise
+
+
#### PromiseInterface::catch()
+
+
```php
+
$promise->catch(callable $onRejected);
+
```
+
+
Registers a rejection handler for promise. It is a shortcut for:
+
+
```php
+
$promise->then(null, $onRejected);
+
```
+
+
Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
+
only specific errors.
+
+
```php
+
$promise
+
->catch(function (\RuntimeException $reason) {
+
// Only catch \RuntimeException instances
+
// All other types of errors will propagate automatically
+
})
+
->catch(function (\Throwable $reason) {
+
// Catch other errors
+
});
+
```
+
+
#### PromiseInterface::finally()
+
+
```php
+
$newPromise = $promise->finally(callable $onFulfilledOrRejected);
+
```
+
+
Allows you to execute "cleanup" type tasks in a promise chain.
+
+
It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
+
when the promise is either fulfilled or rejected.
+
+
* If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
+
`$newPromise` will fulfill with the same value as `$promise`.
+
* If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
+
rejected promise, `$newPromise` will reject with the thrown exception or
+
rejected promise's reason.
+
* If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
+
`$newPromise` will reject with the same reason as `$promise`.
+
* If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
+
rejected promise, `$newPromise` will reject with the thrown exception or
+
rejected promise's reason.
+
+
`finally()` behaves similarly to the synchronous finally statement. When combined
+
with `catch()`, `finally()` allows you to write code that is similar to the familiar
+
synchronous catch/finally pair.
+
+
Consider the following synchronous code:
+
+
```php
+
try {
+
return doSomething();
+
} catch (\Throwable $e) {
+
return handleError($e);
+
} finally {
+
cleanup();
+
}
+
```
+
+
Similar asynchronous code (with `doSomething()` that returns a promise) can be
+
written:
+
+
```php
+
return doSomething()
+
->catch('handleError')
+
->finally('cleanup');
+
```
+
+
#### PromiseInterface::cancel()
+
+
``` php
+
$promise->cancel();
+
```
+
+
The `cancel()` method notifies the creator of the promise that there is no
+
further interest in the results of the operation.
+
+
Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
+
a promise has no effect.
+
+
#### ~~PromiseInterface::otherwise()~~
+
+
> Deprecated since v3.0.0, see [`catch()`](#promiseinterfacecatch) instead.
+
+
The `otherwise()` method registers a rejection handler for a promise.
+
+
This method continues to exist only for BC reasons and to ease upgrading
+
between versions. It is an alias for:
+
+
```php
+
$promise->catch($onRejected);
+
```
+
+
#### ~~PromiseInterface::always()~~
+
+
> Deprecated since v3.0.0, see [`finally()`](#promiseinterfacefinally) instead.
+
+
The `always()` method allows you to execute "cleanup" type tasks in a promise chain.
+
+
This method continues to exist only for BC reasons and to ease upgrading
+
between versions. It is an alias for:
+
+
```php
+
$promise->finally($onFulfilledOrRejected);
+
```
+
+
### Promise
+
+
Creates a promise whose state is controlled by the functions passed to
+
`$resolver`.
+
+
```php
+
$resolver = function (callable $resolve, callable $reject) {
+
// Do some work, possibly asynchronously, and then
+
// resolve or reject.
+
+
$resolve($awesomeResult);
+
// or throw new Exception('Promise rejected');
+
// or $resolve($anotherPromise);
+
// or $reject($nastyError);
+
};
+
+
$canceller = function () {
+
// Cancel/abort any running operations like network connections, streams etc.
+
+
// Reject promise by throwing an exception
+
throw new Exception('Promise cancelled');
+
};
+
+
$promise = new React\Promise\Promise($resolver, $canceller);
+
```
+
+
The promise constructor receives a resolver function and an optional canceller
+
function which both will be called with two arguments:
+
+
* `$resolve($value)` - Primary function that seals the fate of the
+
returned promise. Accepts either a non-promise value, or another promise.
+
When called with a non-promise value, fulfills promise with that value.
+
When called with another promise, e.g. `$resolve($otherPromise)`, promise's
+
fate will be equivalent to that of `$otherPromise`.
+
* `$reject($reason)` - Function that rejects the promise. It is recommended to
+
just throw an exception instead of using `$reject()`.
+
+
If the resolver or canceller throw an exception, the promise will be rejected
+
with that thrown exception as the rejection reason.
+
+
The resolver function will be called immediately, the canceller function only
+
once all consumers called the `cancel()` method of the promise.
+
+
### Functions
+
+
Useful functions for creating and joining collections of promises.
+
+
All functions working on promise collections (like `all()`, `race()`,
+
etc.) support cancellation. This means, if you call `cancel()` on the returned
+
promise, all promises in the collection are cancelled.
+
+
#### resolve()
+
+
```php
+
$promise = React\Promise\resolve(mixed $promiseOrValue);
+
```
+
+
Creates a promise for the supplied `$promiseOrValue`.
+
+
If `$promiseOrValue` is a value, it will be the resolution value of the
+
returned promise.
+
+
If `$promiseOrValue` is a thenable (any object that provides a `then()` method),
+
a trusted promise that follows the state of the thenable is returned.
+
+
If `$promiseOrValue` is a promise, it will be returned as is.
+
+
The resulting `$promise` implements the [`PromiseInterface`](#promiseinterface)
+
and can be consumed like any other promise:
+
+
```php
+
$promise = React\Promise\resolve(42);
+
+
$promise->then(function (int $result): void {
+
var_dump($result);
+
}, function (\Throwable $e): void {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
});
+
```
+
+
#### reject()
+
+
```php
+
$promise = React\Promise\reject(\Throwable $reason);
+
```
+
+
Creates a rejected promise for the supplied `$reason`.
+
+
Note that the [`\Throwable`](https://www.php.net/manual/en/class.throwable.php) interface introduced in PHP 7 covers
+
both user land [`\Exception`](https://www.php.net/manual/en/class.exception.php)'s and
+
[`\Error`](https://www.php.net/manual/en/class.error.php) internal PHP errors. By enforcing `\Throwable` as reason to
+
reject a promise, any language error or user land exception can be used to reject a promise.
+
+
The resulting `$promise` implements the [`PromiseInterface`](#promiseinterface)
+
and can be consumed like any other promise:
+
+
```php
+
$promise = React\Promise\reject(new RuntimeException('Request failed'));
+
+
$promise->then(function (int $result): void {
+
var_dump($result);
+
}, function (\Throwable $e): void {
+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
+
});
+
```
+
+
Note that rejected promises should always be handled similar to how any
+
exceptions should always be caught in a `try` + `catch` block. If you remove the
+
last reference to a rejected promise that has not been handled, it will
+
report an unhandled promise rejection:
+
+
```php
+
function incorrect(): int
+
{
+
$promise = React\Promise\reject(new RuntimeException('Request failed'));
+
+
// Commented out: No rejection handler registered here.
+
// $promise->then(null, function (\Throwable $e): void { /* ignore */ });
+
+
// Returning from a function will remove all local variable references, hence why
+
// this will report an unhandled promise rejection here.
+
return 42;
+
}
+
+
// Calling this function will log an error message plus its stack trace:
+
// Unhandled promise rejection with RuntimeException: Request failed in example.php:10
+
incorrect();
+
```
+
+
A rejected promise will be considered "handled" if you catch the rejection
+
reason with either the [`then()` method](#promiseinterfacethen), the
+
[`catch()` method](#promiseinterfacecatch), or the
+
[`finally()` method](#promiseinterfacefinally). Note that each of these methods
+
return a new promise that may again be rejected if you re-throw an exception.
+
+
A rejected promise will also be considered "handled" if you abort the operation
+
with the [`cancel()` method](#promiseinterfacecancel) (which in turn would
+
usually reject the promise if it is still pending).
+
+
See also the [`set_rejection_handler()` function](#set_rejection_handler).
+
+
#### all()
+
+
```php
+
$promise = React\Promise\all(iterable $promisesOrValues);
+
```
+
+
Returns a promise that will resolve only once all the items in
+
`$promisesOrValues` have resolved. The resolution value of the returned promise
+
will be an array containing the resolution values of each of the items in
+
`$promisesOrValues`.
+
+
#### race()
+
+
```php
+
$promise = React\Promise\race(iterable $promisesOrValues);
+
```
+
+
Initiates a competitive race that allows one winner. Returns a promise which is
+
resolved in the same way the first settled promise resolves.
+
+
The returned promise will become **infinitely pending** if `$promisesOrValues`
+
contains 0 items.
+
+
#### any()
+
+
```php
+
$promise = React\Promise\any(iterable $promisesOrValues);
+
```
+
+
Returns a promise that will resolve when any one of the items in
+
`$promisesOrValues` resolves. The resolution value of the returned promise
+
will be the resolution value of the triggering item.
+
+
The returned promise will only reject if *all* items in `$promisesOrValues` are
+
rejected. The rejection value will be a `React\Promise\Exception\CompositeException`
+
which holds all rejection reasons. The rejection reasons can be obtained with
+
`CompositeException::getThrowables()`.
+
+
The returned promise will also reject with a `React\Promise\Exception\LengthException`
+
if `$promisesOrValues` contains 0 items.
+
+
#### set_rejection_handler()
+
+
```php
+
React\Promise\set_rejection_handler(?callable $callback): ?callable;
+
```
+
+
Sets the global rejection handler for unhandled promise rejections.
+
+
Note that rejected promises should always be handled similar to how any
+
exceptions should always be caught in a `try` + `catch` block. If you remove
+
the last reference to a rejected promise that has not been handled, it will
+
report an unhandled promise rejection. See also the [`reject()` function](#reject)
+
for more details.
+
+
The `?callable $callback` argument MUST be a valid callback function that
+
accepts a single `Throwable` argument or a `null` value to restore the
+
default promise rejection handler. The return value of the callback function
+
will be ignored and has no effect, so you SHOULD return a `void` value. The
+
callback function MUST NOT throw or the program will be terminated with a
+
fatal error.
+
+
The function returns the previous rejection handler or `null` if using the
+
default promise rejection handler.
+
+
The default promise rejection handler will log an error message plus its stack
+
trace:
+
+
```php
+
// Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
+
React\Promise\reject(new RuntimeException('Unhandled'));
+
```
+
+
The promise rejection handler may be used to use customize the log message or
+
write to custom log targets. As a rule of thumb, this function should only be
+
used as a last resort and promise rejections are best handled with either the
+
[`then()` method](#promiseinterfacethen), the
+
[`catch()` method](#promiseinterfacecatch), or the
+
[`finally()` method](#promiseinterfacefinally).
+
See also the [`reject()` function](#reject) for more details.
+
+
Examples
+
--------
+
+
### How to use Deferred
+
+
```php
+
function getAwesomeResultPromise()
+
{
+
$deferred = new React\Promise\Deferred();
+
+
// Execute a Node.js-style function using the callback pattern
+
computeAwesomeResultAsynchronously(function (\Throwable $error, $result) use ($deferred) {
+
if ($error) {
+
$deferred->reject($error);
+
} else {
+
$deferred->resolve($result);
+
}
+
});
+
+
// Return the promise
+
return $deferred->promise();
+
}
+
+
getAwesomeResultPromise()
+
->then(
+
function ($value) {
+
// Deferred resolved, do something with $value
+
},
+
function (\Throwable $reason) {
+
// Deferred rejected, do something with $reason
+
}
+
);
+
```
+
+
### How promise forwarding works
+
+
A few simple examples to show how the mechanics of Promises/A forwarding works.
+
These examples are contrived, of course, and in real usage, promise chains will
+
typically be spread across several function calls, or even several levels of
+
your application architecture.
+
+
#### Resolution forwarding
+
+
Resolved promises forward resolution values to the next promise.
+
The first promise, `$deferred->promise()`, will resolve with the value passed
+
to `$deferred->resolve()` below.
+
+
Each call to `then()` returns a new promise that will resolve with the return
+
value of the previous handler. This creates a promise "pipeline".
+
+
```php
+
$deferred = new React\Promise\Deferred();
+
+
$deferred->promise()
+
->then(function ($x) {
+
// $x will be the value passed to $deferred->resolve() below
+
// and returns a *new promise* for $x + 1
+
return $x + 1;
+
})
+
->then(function ($x) {
+
// $x === 2
+
// This handler receives the return value of the
+
// previous handler.
+
return $x + 1;
+
})
+
->then(function ($x) {
+
// $x === 3
+
// This handler receives the return value of the
+
// previous handler.
+
return $x + 1;
+
})
+
->then(function ($x) {
+
// $x === 4
+
// This handler receives the return value of the
+
// previous handler.
+
echo 'Resolve ' . $x;
+
});
+
+
$deferred->resolve(1); // Prints "Resolve 4"
+
```
+
+
#### Rejection forwarding
+
+
Rejected promises behave similarly, and also work similarly to try/catch:
+
When you catch an exception, you must rethrow for it to propagate.
+
+
Similarly, when you handle a rejected promise, to propagate the rejection,
+
"rethrow" it by either returning a rejected promise, or actually throwing
+
(since promise translates thrown exceptions into rejections)
+
+
```php
+
$deferred = new React\Promise\Deferred();
+
+
$deferred->promise()
+
->then(function ($x) {
+
throw new \Exception($x + 1);
+
})
+
->catch(function (\Exception $x) {
+
// Propagate the rejection
+
throw $x;
+
})
+
->catch(function (\Exception $x) {
+
// Can also propagate by returning another rejection
+
return React\Promise\reject(
+
new \Exception($x->getMessage() + 1)
+
);
+
})
+
->catch(function ($x) {
+
echo 'Reject ' . $x->getMessage(); // 3
+
});
+
+
$deferred->resolve(1); // Prints "Reject 3"
+
```
+
+
#### Mixed resolution and rejection forwarding
+
+
Just like try/catch, you can choose to propagate or not. Mixing resolutions and
+
rejections will still forward handler results in a predictable way.
+
+
```php
+
$deferred = new React\Promise\Deferred();
+
+
$deferred->promise()
+
->then(function ($x) {
+
return $x + 1;
+
})
+
->then(function ($x) {
+
throw new \Exception($x + 1);
+
})
+
->catch(function (\Exception $x) {
+
// Handle the rejection, and don't propagate.
+
// This is like catch without a rethrow
+
return $x->getMessage() + 1;
+
})
+
->then(function ($x) {
+
echo 'Mixed ' . $x; // 4
+
});
+
+
$deferred->resolve(1); // Prints "Mixed 4"
+
```
+
+
Install
+
-------
+
+
The recommended way to install this library is [through Composer](https://getcomposer.org/).
+
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+
This project follows [SemVer](https://semver.org/).
+
This will install the latest supported version from this branch:
+
+
```bash
+
composer require react/promise:^3.2
+
```
+
+
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+
This project aims to run on any platform and thus does not require any PHP
+
extensions and supports running on PHP 7.1 through current PHP 8+.
+
It's *highly recommended to use the latest supported PHP version* for this project.
+
+
We're committed to providing long-term support (LTS) options and to provide a
+
smooth upgrade path. If you're using an older PHP version, you may use the
+
[`2.x` branch](https://github.com/reactphp/promise/tree/2.x) (PHP 5.4+) or
+
[`1.x` branch](https://github.com/reactphp/promise/tree/1.x) (PHP 5.3+) which both
+
provide a compatible API but do not take advantage of newer language features.
+
You may target multiple versions at the same time to support a wider range of
+
PHP versions like this:
+
+
```bash
+
composer require "react/promise:^3 || ^2 || ^1"
+
```
+
+
## Tests
+
+
To run the test suite, you first need to clone this repo and then install all
+
dependencies [through Composer](https://getcomposer.org/):
+
+
```bash
+
composer install
+
```
+
+
To run the test suite, go to the project root and run:
+
+
```bash
+
vendor/bin/phpunit
+
```
+
+
On top of this, we use PHPStan on max level to ensure type safety across the project:
+
+
```bash
+
vendor/bin/phpstan
+
```
+
+
Credits
+
-------
+
+
Promise is a port of [when.js](https://github.com/cujojs/when)
+
by [Brian Cavalier](https://github.com/briancavalier).
+
+
Also, large parts of the documentation have been ported from the when.js
+
[Wiki](https://github.com/cujojs/when/wiki) and the
+
[API docs](https://github.com/cujojs/when/blob/master/docs/api.md).
+
+
License
+
-------
+
+
Released under the [MIT](LICENSE) license.
+57
vendor/react/promise/composer.json
···
+
{
+
"name": "react/promise",
+
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
+
"license": "MIT",
+
"authors": [
+
{
+
"name": "Jan Sorgalla",
+
"homepage": "https://sorgalla.com/",
+
"email": "jsorgalla@gmail.com"
+
},
+
{
+
"name": "Christian Lück",
+
"homepage": "https://clue.engineering/",
+
"email": "christian@clue.engineering"
+
},
+
{
+
"name": "Cees-Jan Kiewiet",
+
"homepage": "https://wyrihaximus.net/",
+
"email": "reactphp@ceesjankiewiet.nl"
+
},
+
{
+
"name": "Chris Boden",
+
"homepage": "https://cboden.dev/",
+
"email": "cboden@gmail.com"
+
}
+
],
+
"require": {
+
"php": ">=7.1.0"
+
},
+
"require-dev": {
+
"phpstan/phpstan": "1.12.28 || 1.4.10",
+
"phpunit/phpunit": "^9.6 || ^7.5"
+
},
+
"autoload": {
+
"psr-4": {
+
"React\\Promise\\": "src/"
+
},
+
"files": [
+
"src/functions_include.php"
+
]
+
},
+
"autoload-dev": {
+
"psr-4": {
+
"React\\Promise\\": [
+
"tests/fixtures/",
+
"tests/"
+
]
+
},
+
"files": [
+
"tests/Fiber.php"
+
]
+
},
+
"keywords": [
+
"promise",
+
"promises"
+
]
+
}
+52
vendor/react/promise/src/Deferred.php
···
+
<?php
+
+
namespace React\Promise;
+
+
/**
+
* @template T
+
*/
+
final class Deferred
+
{
+
/**
+
* @var PromiseInterface<T>
+
*/
+
private $promise;
+
+
/** @var callable(T):void */
+
private $resolveCallback;
+
+
/** @var callable(\Throwable):void */
+
private $rejectCallback;
+
+
/**
+
* @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller
+
*/
+
public function __construct(?callable $canceller = null)
+
{
+
$this->promise = new Promise(function ($resolve, $reject): void {
+
$this->resolveCallback = $resolve;
+
$this->rejectCallback = $reject;
+
}, $canceller);
+
}
+
+
/**
+
* @return PromiseInterface<T>
+
*/
+
public function promise(): PromiseInterface
+
{
+
return $this->promise;
+
}
+
+
/**
+
* @param T $value
+
*/
+
public function resolve($value): void
+
{
+
($this->resolveCallback)($value);
+
}
+
+
public function reject(\Throwable $reason): void
+
{
+
($this->rejectCallback)($reason);
+
}
+
}
+32
vendor/react/promise/src/Exception/CompositeException.php
···
+
<?php
+
+
namespace React\Promise\Exception;
+
+
/**
+
* Represents an exception that is a composite of one or more other exceptions.
+
*
+
* This exception is useful in situations where a promise must be rejected
+
* with multiple exceptions. It is used for example to reject the returned
+
* promise from `some()` and `any()` when too many input promises reject.
+
*/
+
class CompositeException extends \Exception
+
{
+
/** @var \Throwable[] */
+
private $throwables;
+
+
/** @param \Throwable[] $throwables */
+
public function __construct(array $throwables, string $message = '', int $code = 0, ?\Throwable $previous = null)
+
{
+
parent::__construct($message, $code, $previous);
+
+
$this->throwables = $throwables;
+
}
+
+
/**
+
* @return \Throwable[]
+
*/
+
public function getThrowables(): array
+
{
+
return $this->throwables;
+
}
+
}
+7
vendor/react/promise/src/Exception/LengthException.php
···
+
<?php
+
+
namespace React\Promise\Exception;
+
+
class LengthException extends \LengthException
+
{
+
}
+64
vendor/react/promise/src/Internal/CancellationQueue.php
···
+
<?php
+
+
namespace React\Promise\Internal;
+
+
/**
+
* @internal
+
*/
+
final class CancellationQueue
+
{
+
/** @var bool */
+
private $started = false;
+
+
/** @var object[] */
+
private $queue = [];
+
+
public function __invoke(): void
+
{
+
if ($this->started) {
+
return;
+
}
+
+
$this->started = true;
+
$this->drain();
+
}
+
+
/**
+
* @param mixed $cancellable
+
*/
+
public function enqueue($cancellable): void
+
{
+
if (!\is_object($cancellable) || !\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) {
+
return;
+
}
+
+
$length = \array_push($this->queue, $cancellable);
+
+
if ($this->started && 1 === $length) {
+
$this->drain();
+
}
+
}
+
+
private function drain(): void
+
{
+
for ($i = \key($this->queue); isset($this->queue[$i]); $i++) {
+
$cancellable = $this->queue[$i];
+
assert(\method_exists($cancellable, 'cancel'));
+
+
$exception = null;
+
+
try {
+
$cancellable->cancel();
+
} catch (\Throwable $exception) {
+
}
+
+
unset($this->queue[$i]);
+
+
if ($exception) {
+
throw $exception;
+
}
+
}
+
+
$this->queue = [];
+
}
+
}
+90
vendor/react/promise/src/Internal/FulfilledPromise.php
···
+
<?php
+
+
namespace React\Promise\Internal;
+
+
use React\Promise\PromiseInterface;
+
use function React\Promise\resolve;
+
+
/**
+
* @internal
+
*
+
* @template T
+
* @template-implements PromiseInterface<T>
+
*/
+
final class FulfilledPromise implements PromiseInterface
+
{
+
/** @var T */
+
private $value;
+
+
/**
+
* @param T $value
+
* @throws \InvalidArgumentException
+
*/
+
public function __construct($value = null)
+
{
+
if ($value instanceof PromiseInterface) {
+
throw new \InvalidArgumentException('You cannot create React\Promise\FulfilledPromise with a promise. Use React\Promise\resolve($promiseOrValue) instead.');
+
}
+
+
$this->value = $value;
+
}
+
+
/**
+
* @template TFulfilled
+
* @param ?(callable((T is void ? null : T)): (PromiseInterface<TFulfilled>|TFulfilled)) $onFulfilled
+
* @return PromiseInterface<($onFulfilled is null ? T : TFulfilled)>
+
*/
+
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface
+
{
+
if (null === $onFulfilled) {
+
return $this;
+
}
+
+
try {
+
/**
+
* @var PromiseInterface<T>|T $result
+
*/
+
$result = $onFulfilled($this->value);
+
return resolve($result);
+
} catch (\Throwable $exception) {
+
return new RejectedPromise($exception);
+
}
+
}
+
+
public function catch(callable $onRejected): PromiseInterface
+
{
+
return $this;
+
}
+
+
public function finally(callable $onFulfilledOrRejected): PromiseInterface
+
{
+
return $this->then(function ($value) use ($onFulfilledOrRejected): PromiseInterface {
+
/** @var T $value */
+
return resolve($onFulfilledOrRejected())->then(function () use ($value) {
+
return $value;
+
});
+
});
+
}
+
+
public function cancel(): void
+
{
+
}
+
+
/**
+
* @deprecated 3.0.0 Use `catch()` instead
+
* @see self::catch()
+
*/
+
public function otherwise(callable $onRejected): PromiseInterface
+
{
+
return $this->catch($onRejected);
+
}
+
+
/**
+
* @deprecated 3.0.0 Use `finally()` instead
+
* @see self::finally()
+
*/
+
public function always(callable $onFulfilledOrRejected): PromiseInterface
+
{
+
return $this->finally($onFulfilledOrRejected);
+
}
+
}
+128
vendor/react/promise/src/Internal/RejectedPromise.php
···
+
<?php
+
+
namespace React\Promise\Internal;
+
+
use React\Promise\PromiseInterface;
+
use function React\Promise\_checkTypehint;
+
use function React\Promise\resolve;
+
use function React\Promise\set_rejection_handler;
+
+
/**
+
* @internal
+
*
+
* @template-implements PromiseInterface<never>
+
*/
+
final class RejectedPromise implements PromiseInterface
+
{
+
/** @var \Throwable */
+
private $reason;
+
+
/** @var bool */
+
private $handled = false;
+
+
/**
+
* @param \Throwable $reason
+
*/
+
public function __construct(\Throwable $reason)
+
{
+
$this->reason = $reason;
+
}
+
+
/** @throws void */
+
public function __destruct()
+
{
+
if ($this->handled) {
+
return;
+
}
+
+
$handler = set_rejection_handler(null);
+
if ($handler === null) {
+
$message = 'Unhandled promise rejection with ' . $this->reason;
+
+
\error_log($message);
+
return;
+
}
+
+
try {
+
$handler($this->reason);
+
} catch (\Throwable $e) {
+
\preg_match('/^([^:\s]++)(.*+)$/sm', (string) $e, $match);
+
\assert(isset($match[1], $match[2]));
+
$message = 'Fatal error: Uncaught ' . $match[1] . ' from unhandled promise rejection handler' . $match[2];
+
+
\error_log($message);
+
exit(255);
+
}
+
}
+
+
/**
+
* @template TRejected
+
* @param ?callable $onFulfilled
+
* @param ?(callable(\Throwable): (PromiseInterface<TRejected>|TRejected)) $onRejected
+
* @return PromiseInterface<($onRejected is null ? never : TRejected)>
+
*/
+
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface
+
{
+
if (null === $onRejected) {
+
return $this;
+
}
+
+
$this->handled = true;
+
+
try {
+
return resolve($onRejected($this->reason));
+
} catch (\Throwable $exception) {
+
return new RejectedPromise($exception);
+
}
+
}
+
+
/**
+
* @template TThrowable of \Throwable
+
* @template TRejected
+
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
+
* @return PromiseInterface<TRejected>
+
*/
+
public function catch(callable $onRejected): PromiseInterface
+
{
+
if (!_checkTypehint($onRejected, $this->reason)) {
+
return $this;
+
}
+
+
/**
+
* @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected
+
*/
+
return $this->then(null, $onRejected);
+
}
+
+
public function finally(callable $onFulfilledOrRejected): PromiseInterface
+
{
+
return $this->then(null, function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface {
+
return resolve($onFulfilledOrRejected())->then(function () use ($reason): PromiseInterface {
+
return new RejectedPromise($reason);
+
});
+
});
+
}
+
+
public function cancel(): void
+
{
+
$this->handled = true;
+
}
+
+
/**
+
* @deprecated 3.0.0 Use `catch()` instead
+
* @see self::catch()
+
*/
+
public function otherwise(callable $onRejected): PromiseInterface
+
{
+
return $this->catch($onRejected);
+
}
+
+
/**
+
* @deprecated 3.0.0 Use `always()` instead
+
* @see self::always()
+
*/
+
public function always(callable $onFulfilledOrRejected): PromiseInterface
+
{
+
return $this->finally($onFulfilledOrRejected);
+
}
+
}
+304
vendor/react/promise/src/Promise.php
···
+
<?php
+
+
namespace React\Promise;
+
+
use React\Promise\Internal\RejectedPromise;
+
+
/**
+
* @template T
+
* @template-implements PromiseInterface<T>
+
*/
+
final class Promise implements PromiseInterface
+
{
+
/** @var (callable(callable(T):void,callable(\Throwable):void):void)|null */
+
private $canceller;
+
+
/** @var ?PromiseInterface<T> */
+
private $result;
+
+
/** @var list<callable(PromiseInterface<T>):void> */
+
private $handlers = [];
+
+
/** @var int */
+
private $requiredCancelRequests = 0;
+
+
/** @var bool */
+
private $cancelled = false;
+
+
/**
+
* @param callable(callable(T):void,callable(\Throwable):void):void $resolver
+
* @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller
+
*/
+
public function __construct(callable $resolver, ?callable $canceller = null)
+
{
+
$this->canceller = $canceller;
+
+
// Explicitly overwrite arguments with null values before invoking
+
// resolver function. This ensure that these arguments do not show up
+
// in the stack trace in PHP 7+ only.
+
$cb = $resolver;
+
$resolver = $canceller = null;
+
$this->call($cb);
+
}
+
+
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface
+
{
+
if (null !== $this->result) {
+
return $this->result->then($onFulfilled, $onRejected);
+
}
+
+
if (null === $this->canceller) {
+
return new static($this->resolver($onFulfilled, $onRejected));
+
}
+
+
// This promise has a canceller, so we create a new child promise which
+
// has a canceller that invokes the parent canceller if all other
+
// followers are also cancelled. We keep a reference to this promise
+
// instance for the static canceller function and clear this to avoid
+
// keeping a cyclic reference between parent and follower.
+
$parent = $this;
+
++$parent->requiredCancelRequests;
+
+
return new static(
+
$this->resolver($onFulfilled, $onRejected),
+
static function () use (&$parent): void {
+
assert($parent instanceof self);
+
--$parent->requiredCancelRequests;
+
+
if ($parent->requiredCancelRequests <= 0) {
+
$parent->cancel();
+
}
+
+
$parent = null;
+
}
+
);
+
}
+
+
/**
+
* @template TThrowable of \Throwable
+
* @template TRejected
+
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
+
* @return PromiseInterface<T|TRejected>
+
*/
+
public function catch(callable $onRejected): PromiseInterface
+
{
+
return $this->then(null, static function (\Throwable $reason) use ($onRejected) {
+
if (!_checkTypehint($onRejected, $reason)) {
+
return new RejectedPromise($reason);
+
}
+
+
/**
+
* @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected
+
*/
+
return $onRejected($reason);
+
});
+
}
+
+
public function finally(callable $onFulfilledOrRejected): PromiseInterface
+
{
+
return $this->then(static function ($value) use ($onFulfilledOrRejected): PromiseInterface {
+
/** @var T $value */
+
return resolve($onFulfilledOrRejected())->then(function () use ($value) {
+
return $value;
+
});
+
}, static function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface {
+
return resolve($onFulfilledOrRejected())->then(function () use ($reason): RejectedPromise {
+
return new RejectedPromise($reason);
+
});
+
});
+
}
+
+
public function cancel(): void
+
{
+
$this->cancelled = true;
+
$canceller = $this->canceller;
+
$this->canceller = null;
+
+
$parentCanceller = null;
+
+
if (null !== $this->result) {
+
// Forward cancellation to rejected promise to avoid reporting unhandled rejection
+
if ($this->result instanceof RejectedPromise) {
+
$this->result->cancel();
+
}
+
+
// Go up the promise chain and reach the top most promise which is
+
// itself not following another promise
+
$root = $this->unwrap($this->result);
+
+
// Return if the root promise is already resolved or a
+
// FulfilledPromise or RejectedPromise
+
if (!$root instanceof self || null !== $root->result) {
+
return;
+
}
+
+
$root->requiredCancelRequests--;
+
+
if ($root->requiredCancelRequests <= 0) {
+
$parentCanceller = [$root, 'cancel'];
+
}
+
}
+
+
if (null !== $canceller) {
+
$this->call($canceller);
+
}
+
+
// For BC, we call the parent canceller after our own canceller
+
if ($parentCanceller) {
+
$parentCanceller();
+
}
+
}
+
+
/**
+
* @deprecated 3.0.0 Use `catch()` instead
+
* @see self::catch()
+
*/
+
public function otherwise(callable $onRejected): PromiseInterface
+
{
+
return $this->catch($onRejected);
+
}
+
+
/**
+
* @deprecated 3.0.0 Use `finally()` instead
+
* @see self::finally()
+
*/
+
public function always(callable $onFulfilledOrRejected): PromiseInterface
+
{
+
return $this->finally($onFulfilledOrRejected);
+
}
+
+
private function resolver(?callable $onFulfilled = null, ?callable $onRejected = null): callable
+
{
+
return function (callable $resolve, callable $reject) use ($onFulfilled, $onRejected): void {
+
$this->handlers[] = static function (PromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject): void {
+
$promise = $promise->then($onFulfilled, $onRejected);
+
+
if ($promise instanceof self && $promise->result === null) {
+
$promise->handlers[] = static function (PromiseInterface $promise) use ($resolve, $reject): void {
+
$promise->then($resolve, $reject);
+
};
+
} else {
+
$promise->then($resolve, $reject);
+
}
+
};
+
};
+
}
+
+
private function reject(\Throwable $reason): void
+
{
+
if (null !== $this->result) {
+
return;
+
}
+
+
$this->settle(reject($reason));
+
}
+
+
/**
+
* @param PromiseInterface<T> $result
+
*/
+
private function settle(PromiseInterface $result): void
+
{
+
$result = $this->unwrap($result);
+
+
if ($result === $this) {
+
$result = new RejectedPromise(
+
new \LogicException('Cannot resolve a promise with itself.')
+
);
+
}
+
+
if ($result instanceof self) {
+
$result->requiredCancelRequests++;
+
} else {
+
// Unset canceller only when not following a pending promise
+
$this->canceller = null;
+
}
+
+
$handlers = $this->handlers;
+
+
$this->handlers = [];
+
$this->result = $result;
+
+
foreach ($handlers as $handler) {
+
$handler($result);
+
}
+
+
// Forward cancellation to rejected promise to avoid reporting unhandled rejection
+
if ($this->cancelled && $result instanceof RejectedPromise) {
+
$result->cancel();
+
}
+
}
+
+
/**
+
* @param PromiseInterface<T> $promise
+
* @return PromiseInterface<T>
+
*/
+
private function unwrap(PromiseInterface $promise): PromiseInterface
+
{
+
while ($promise instanceof self && null !== $promise->result) {
+
/** @var PromiseInterface<T> $promise */
+
$promise = $promise->result;
+
}
+
+
return $promise;
+
}
+
+
/**
+
* @param callable(callable(mixed):void,callable(\Throwable):void):void $cb
+
*/
+
private function call(callable $cb): void
+
{
+
// Explicitly overwrite argument with null value. This ensure that this
+
// argument does not show up in the stack trace in PHP 7+ only.
+
$callback = $cb;
+
$cb = null;
+
+
// Use reflection to inspect number of arguments expected by this callback.
+
// We did some careful benchmarking here: Using reflection to avoid unneeded
+
// function arguments is actually faster than blindly passing them.
+
// Also, this helps avoiding unnecessary function arguments in the call stack
+
// if the callback creates an Exception (creating garbage cycles).
+
if (\is_array($callback)) {
+
$ref = new \ReflectionMethod($callback[0], $callback[1]);
+
} elseif (\is_object($callback) && !$callback instanceof \Closure) {
+
$ref = new \ReflectionMethod($callback, '__invoke');
+
} else {
+
assert($callback instanceof \Closure || \is_string($callback));
+
$ref = new \ReflectionFunction($callback);
+
}
+
$args = $ref->getNumberOfParameters();
+
+
try {
+
if ($args === 0) {
+
$callback();
+
} else {
+
// Keep references to this promise instance for the static resolve/reject functions.
+
// By using static callbacks that are not bound to this instance
+
// and passing the target promise instance by reference, we can
+
// still execute its resolving logic and still clear this
+
// reference when settling the promise. This helps avoiding
+
// garbage cycles if any callback creates an Exception.
+
// These assumptions are covered by the test suite, so if you ever feel like
+
// refactoring this, go ahead, any alternative suggestions are welcome!
+
$target =& $this;
+
+
$callback(
+
static function ($value) use (&$target): void {
+
if ($target !== null) {
+
$target->settle(resolve($value));
+
$target = null;
+
}
+
},
+
static function (\Throwable $reason) use (&$target): void {
+
if ($target !== null) {
+
$target->reject($reason);
+
$target = null;
+
}
+
}
+
);
+
}
+
} catch (\Throwable $e) {
+
$target = null;
+
$this->reject($e);
+
}
+
}
+
}
+152
vendor/react/promise/src/PromiseInterface.php
···
+
<?php
+
+
namespace React\Promise;
+
+
/**
+
* @template-covariant T
+
*/
+
interface PromiseInterface
+
{
+
/**
+
* Transforms a promise's value by applying a function to the promise's fulfillment
+
* or rejection value. Returns a new promise for the transformed result.
+
*
+
* The `then()` method registers new fulfilled and rejection handlers with a promise
+
* (all parameters are optional):
+
*
+
* * `$onFulfilled` will be invoked once the promise is fulfilled and passed
+
* the result as the first argument.
+
* * `$onRejected` will be invoked once the promise is rejected and passed the
+
* reason as the first argument.
+
*
+
* It returns a new promise that will fulfill with the return value of either
+
* `$onFulfilled` or `$onRejected`, whichever is called, or will reject with
+
* the thrown exception if either throws.
+
*
+
* A promise makes the following guarantees about handlers registered in
+
* the same call to `then()`:
+
*
+
* 1. Only one of `$onFulfilled` or `$onRejected` will be called,
+
* never both.
+
* 2. `$onFulfilled` and `$onRejected` will never be called more
+
* than once.
+
*
+
* @template TFulfilled
+
* @template TRejected
+
* @param ?(callable((T is void ? null : T)): (PromiseInterface<TFulfilled>|TFulfilled)) $onFulfilled
+
* @param ?(callable(\Throwable): (PromiseInterface<TRejected>|TRejected)) $onRejected
+
* @return PromiseInterface<($onRejected is null ? ($onFulfilled is null ? T : TFulfilled) : ($onFulfilled is null ? T|TRejected : TFulfilled|TRejected))>
+
*/
+
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface;
+
+
/**
+
* Registers a rejection handler for promise. It is a shortcut for:
+
*
+
* ```php
+
* $promise->then(null, $onRejected);
+
* ```
+
*
+
* Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
+
* only specific errors.
+
*
+
* @template TThrowable of \Throwable
+
* @template TRejected
+
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
+
* @return PromiseInterface<T|TRejected>
+
*/
+
public function catch(callable $onRejected): PromiseInterface;
+
+
/**
+
* Allows you to execute "cleanup" type tasks in a promise chain.
+
*
+
* It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
+
* when the promise is either fulfilled or rejected.
+
*
+
* * If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
+
* `$newPromise` will fulfill with the same value as `$promise`.
+
* * If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
+
* rejected promise, `$newPromise` will reject with the thrown exception or
+
* rejected promise's reason.
+
* * If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
+
* `$newPromise` will reject with the same reason as `$promise`.
+
* * If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
+
* rejected promise, `$newPromise` will reject with the thrown exception or
+
* rejected promise's reason.
+
*
+
* `finally()` behaves similarly to the synchronous finally statement. When combined
+
* with `catch()`, `finally()` allows you to write code that is similar to the familiar
+
* synchronous catch/finally pair.
+
*
+
* Consider the following synchronous code:
+
*
+
* ```php
+
* try {
+
* return doSomething();
+
* } catch(\Exception $e) {
+
* return handleError($e);
+
* } finally {
+
* cleanup();
+
* }
+
* ```
+
*
+
* Similar asynchronous code (with `doSomething()` that returns a promise) can be
+
* written:
+
*
+
* ```php
+
* return doSomething()
+
* ->catch('handleError')
+
* ->finally('cleanup');
+
* ```
+
*
+
* @param callable(): (void|PromiseInterface<void>) $onFulfilledOrRejected
+
* @return PromiseInterface<T>
+
*/
+
public function finally(callable $onFulfilledOrRejected): PromiseInterface;
+
+
/**
+
* The `cancel()` method notifies the creator of the promise that there is no
+
* further interest in the results of the operation.
+
*
+
* Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
+
* a promise has no effect.
+
*
+
* @return void
+
*/
+
public function cancel(): void;
+
+
/**
+
* [Deprecated] Registers a rejection handler for a promise.
+
*
+
* This method continues to exist only for BC reasons and to ease upgrading
+
* between versions. It is an alias for:
+
*
+
* ```php
+
* $promise->catch($onRejected);
+
* ```
+
*
+
* @template TThrowable of \Throwable
+
* @template TRejected
+
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
+
* @return PromiseInterface<T|TRejected>
+
* @deprecated 3.0.0 Use catch() instead
+
* @see self::catch()
+
*/
+
public function otherwise(callable $onRejected): PromiseInterface;
+
+
/**
+
* [Deprecated] Allows you to execute "cleanup" type tasks in a promise chain.
+
*
+
* This method continues to exist only for BC reasons and to ease upgrading
+
* between versions. It is an alias for:
+
*
+
* ```php
+
* $promise->finally($onFulfilledOrRejected);
+
* ```
+
*
+
* @param callable(): (void|PromiseInterface<void>) $onFulfilledOrRejected
+
* @return PromiseInterface<T>
+
* @deprecated 3.0.0 Use finally() instead
+
* @see self::finally()
+
*/
+
public function always(callable $onFulfilledOrRejected): PromiseInterface;
+
}
+345
vendor/react/promise/src/functions.php
···
+
<?php
+
+
namespace React\Promise;
+
+
use React\Promise\Exception\CompositeException;
+
use React\Promise\Internal\FulfilledPromise;
+
use React\Promise\Internal\RejectedPromise;
+
+
/**
+
* Creates a promise for the supplied `$promiseOrValue`.
+
*
+
* If `$promiseOrValue` is a value, it will be the resolution value of the
+
* returned promise.
+
*
+
* If `$promiseOrValue` is a thenable (any object that provides a `then()` method),
+
* a trusted promise that follows the state of the thenable is returned.
+
*
+
* If `$promiseOrValue` is a promise, it will be returned as is.
+
*
+
* @template T
+
* @param PromiseInterface<T>|T $promiseOrValue
+
* @return PromiseInterface<T>
+
*/
+
function resolve($promiseOrValue): PromiseInterface
+
{
+
if ($promiseOrValue instanceof PromiseInterface) {
+
return $promiseOrValue;
+
}
+
+
if (\is_object($promiseOrValue) && \method_exists($promiseOrValue, 'then')) {
+
$canceller = null;
+
+
if (\method_exists($promiseOrValue, 'cancel')) {
+
$canceller = [$promiseOrValue, 'cancel'];
+
assert(\is_callable($canceller));
+
}
+
+
/** @var Promise<T> */
+
return new Promise(function (callable $resolve, callable $reject) use ($promiseOrValue): void {
+
$promiseOrValue->then($resolve, $reject);
+
}, $canceller);
+
}
+
+
return new FulfilledPromise($promiseOrValue);
+
}
+
+
/**
+
* Creates a rejected promise for the supplied `$reason`.
+
*
+
* If `$reason` is a value, it will be the rejection value of the
+
* returned promise.
+
*
+
* If `$reason` is a promise, its completion value will be the rejected
+
* value of the returned promise.
+
*
+
* This can be useful in situations where you need to reject a promise without
+
* throwing an exception. For example, it allows you to propagate a rejection with
+
* the value of another promise.
+
*
+
* @return PromiseInterface<never>
+
*/
+
function reject(\Throwable $reason): PromiseInterface
+
{
+
return new RejectedPromise($reason);
+
}
+
+
/**
+
* Returns a promise that will resolve only once all the items in
+
* `$promisesOrValues` have resolved. The resolution value of the returned promise
+
* will be an array containing the resolution values of each of the items in
+
* `$promisesOrValues`.
+
*
+
* @template T
+
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
+
* @return PromiseInterface<array<T>>
+
*/
+
function all(iterable $promisesOrValues): PromiseInterface
+
{
+
$cancellationQueue = new Internal\CancellationQueue();
+
+
/** @var Promise<array<T>> */
+
return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
+
$toResolve = 0;
+
/** @var bool */
+
$continue = true;
+
$values = [];
+
+
foreach ($promisesOrValues as $i => $promiseOrValue) {
+
$cancellationQueue->enqueue($promiseOrValue);
+
$values[$i] = null;
+
++$toResolve;
+
+
resolve($promiseOrValue)->then(
+
function ($value) use ($i, &$values, &$toResolve, &$continue, $resolve): void {
+
$values[$i] = $value;
+
+
if (0 === --$toResolve && !$continue) {
+
$resolve($values);
+
}
+
},
+
function (\Throwable $reason) use (&$continue, $reject): void {
+
$continue = false;
+
$reject($reason);
+
}
+
);
+
+
if (!$continue && !\is_array($promisesOrValues)) {
+
break;
+
}
+
}
+
+
$continue = false;
+
if ($toResolve === 0) {
+
$resolve($values);
+
}
+
}, $cancellationQueue);
+
}
+
+
/**
+
* Initiates a competitive race that allows one winner. Returns a promise which is
+
* resolved in the same way the first settled promise resolves.
+
*
+
* The returned promise will become **infinitely pending** if `$promisesOrValues`
+
* contains 0 items.
+
*
+
* @template T
+
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
+
* @return PromiseInterface<T>
+
*/
+
function race(iterable $promisesOrValues): PromiseInterface
+
{
+
$cancellationQueue = new Internal\CancellationQueue();
+
+
/** @var Promise<T> */
+
return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
+
$continue = true;
+
+
foreach ($promisesOrValues as $promiseOrValue) {
+
$cancellationQueue->enqueue($promiseOrValue);
+
+
resolve($promiseOrValue)->then($resolve, $reject)->finally(function () use (&$continue): void {
+
$continue = false;
+
});
+
+
if (!$continue && !\is_array($promisesOrValues)) {
+
break;
+
}
+
}
+
}, $cancellationQueue);
+
}
+
+
/**
+
* Returns a promise that will resolve when any one of the items in
+
* `$promisesOrValues` resolves. The resolution value of the returned promise
+
* will be the resolution value of the triggering item.
+
*
+
* The returned promise will only reject if *all* items in `$promisesOrValues` are
+
* rejected. The rejection value will be an array of all rejection reasons.
+
*
+
* The returned promise will also reject with a `React\Promise\Exception\LengthException`
+
* if `$promisesOrValues` contains 0 items.
+
*
+
* @template T
+
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
+
* @return PromiseInterface<T>
+
*/
+
function any(iterable $promisesOrValues): PromiseInterface
+
{
+
$cancellationQueue = new Internal\CancellationQueue();
+
+
/** @var Promise<T> */
+
return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
+
$toReject = 0;
+
$continue = true;
+
$reasons = [];
+
+
foreach ($promisesOrValues as $i => $promiseOrValue) {
+
$cancellationQueue->enqueue($promiseOrValue);
+
++$toReject;
+
+
resolve($promiseOrValue)->then(
+
function ($value) use ($resolve, &$continue): void {
+
$continue = false;
+
$resolve($value);
+
},
+
function (\Throwable $reason) use ($i, &$reasons, &$toReject, $reject, &$continue): void {
+
$reasons[$i] = $reason;
+
+
if (0 === --$toReject && !$continue) {
+
$reject(new CompositeException(
+
$reasons,
+
'All promises rejected.'
+
));
+
}
+
}
+
);
+
+
if (!$continue && !\is_array($promisesOrValues)) {
+
break;
+
}
+
}
+
+
$continue = false;
+
if ($toReject === 0 && !$reasons) {
+
$reject(new Exception\LengthException(
+
'Must contain at least 1 item but contains only 0 items.'
+
));
+
} elseif ($toReject === 0) {
+
$reject(new CompositeException(
+
$reasons,
+
'All promises rejected.'
+
));
+
}
+
}, $cancellationQueue);
+
}
+
+
/**
+
* Sets the global rejection handler for unhandled promise rejections.
+
*
+
* Note that rejected promises should always be handled similar to how any
+
* exceptions should always be caught in a `try` + `catch` block. If you remove
+
* the last reference to a rejected promise that has not been handled, it will
+
* report an unhandled promise rejection. See also the [`reject()` function](#reject)
+
* for more details.
+
*
+
* The `?callable $callback` argument MUST be a valid callback function that
+
* accepts a single `Throwable` argument or a `null` value to restore the
+
* default promise rejection handler. The return value of the callback function
+
* will be ignored and has no effect, so you SHOULD return a `void` value. The
+
* callback function MUST NOT throw or the program will be terminated with a
+
* fatal error.
+
*
+
* The function returns the previous rejection handler or `null` if using the
+
* default promise rejection handler.
+
*
+
* The default promise rejection handler will log an error message plus its
+
* stack trace:
+
*
+
* ```php
+
* // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
+
* React\Promise\reject(new RuntimeException('Unhandled'));
+
* ```
+
*
+
* The promise rejection handler may be used to use customize the log message or
+
* write to custom log targets. As a rule of thumb, this function should only be
+
* used as a last resort and promise rejections are best handled with either the
+
* [`then()` method](#promiseinterfacethen), the
+
* [`catch()` method](#promiseinterfacecatch), or the
+
* [`finally()` method](#promiseinterfacefinally).
+
* See also the [`reject()` function](#reject) for more details.
+
*
+
* @param callable(\Throwable):void|null $callback
+
* @return callable(\Throwable):void|null
+
*/
+
function set_rejection_handler(?callable $callback): ?callable
+
{
+
static $current = null;
+
$previous = $current;
+
$current = $callback;
+
+
return $previous;
+
}
+
+
/**
+
* @internal
+
*/
+
function _checkTypehint(callable $callback, \Throwable $reason): bool
+
{
+
if (\is_array($callback)) {
+
$callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
+
} elseif (\is_object($callback) && !$callback instanceof \Closure) {
+
$callbackReflection = new \ReflectionMethod($callback, '__invoke');
+
} else {
+
assert($callback instanceof \Closure || \is_string($callback));
+
$callbackReflection = new \ReflectionFunction($callback);
+
}
+
+
$parameters = $callbackReflection->getParameters();
+
+
if (!isset($parameters[0])) {
+
return true;
+
}
+
+
$expectedException = $parameters[0];
+
+
// Extract the type of the argument and handle different possibilities
+
$type = $expectedException->getType();
+
+
$isTypeUnion = true;
+
$types = [];
+
+
switch (true) {
+
case $type === null:
+
break;
+
case $type instanceof \ReflectionNamedType:
+
$types = [$type];
+
break;
+
case $type instanceof \ReflectionIntersectionType:
+
$isTypeUnion = false;
+
case $type instanceof \ReflectionUnionType:
+
$types = $type->getTypes();
+
break;
+
default:
+
throw new \LogicException('Unexpected return value of ReflectionParameter::getType');
+
}
+
+
// If there is no type restriction, it matches
+
if (empty($types)) {
+
return true;
+
}
+
+
foreach ($types as $type) {
+
+
if ($type instanceof \ReflectionIntersectionType) {
+
foreach ($type->getTypes() as $typeToMatch) {
+
assert($typeToMatch instanceof \ReflectionNamedType);
+
$name = $typeToMatch->getName();
+
if (!($matches = (!$typeToMatch->isBuiltin() && $reason instanceof $name))) {
+
break;
+
}
+
}
+
assert(isset($matches));
+
} else {
+
assert($type instanceof \ReflectionNamedType);
+
$name = $type->getName();
+
$matches = !$type->isBuiltin() && $reason instanceof $name;
+
}
+
+
// If we look for a single match (union), we can return early on match
+
// If we look for a full match (intersection), we can return early on mismatch
+
if ($matches) {
+
if ($isTypeUnion) {
+
return true;
+
}
+
} else {
+
if (!$isTypeUnion) {
+
return false;
+
}
+
}
+
}
+
+
// If we look for a single match (union) and did not return early, we matched no type and are false
+
// If we look for a full match (intersection) and did not return early, we matched all types and are true
+
return $isTypeUnion ? false : true;
+
}
+5
vendor/react/promise/src/functions_include.php
···
+
<?php
+
+
if (!\function_exists('React\Promise\resolve')) {
+
require __DIR__.'/functions.php';
+
}