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

aaaaaaaaaaaaa

+9109 -142
+3 -1
composer.json
··· 9 9 "chillerlan/php-oauth": "^1.0", 10 10 "tracy/tracy": "^2.10", 11 11 "flightphp/runway": "^1.1", 12 - "fusonic/opengraph": "^3.0" 12 + "fusonic/opengraph": "^3.0", 13 + "react/promise": "^3.2", 14 + "react/async": "^4.3" 13 15 }, 14 16 "require-dev": { 15 17 "flightphp/tracy-extensions": "^0.2.7"
+221 -1
composer.lock
··· 4 4 "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 5 "This file is @generated automatically" 6 6 ], 7 - "content-hash": "084207acd9d27dbf5822b930886b2c3d", 7 + "content-hash": "d1b9a49dbc0044c8b668ec563b93ed20", 8 8 "packages": [ 9 9 { 10 10 "name": "adhocore/cli", ··· 1998 1998 "source": "https://github.com/ralouphie/getallheaders/tree/develop" 1999 1999 }, 2000 2000 "time": "2019-03-08T08:55:37+00:00" 2001 + }, 2002 + { 2003 + "name": "react/async", 2004 + "version": "v4.3.0", 2005 + "source": { 2006 + "type": "git", 2007 + "url": "https://github.com/reactphp/async.git", 2008 + "reference": "635d50e30844a484495713e8cb8d9e079c0008a5" 2009 + }, 2010 + "dist": { 2011 + "type": "zip", 2012 + "url": "https://api.github.com/repos/reactphp/async/zipball/635d50e30844a484495713e8cb8d9e079c0008a5", 2013 + "reference": "635d50e30844a484495713e8cb8d9e079c0008a5", 2014 + "shasum": "" 2015 + }, 2016 + "require": { 2017 + "php": ">=8.1", 2018 + "react/event-loop": "^1.2", 2019 + "react/promise": "^3.2 || ^2.8 || ^1.2.1" 2020 + }, 2021 + "require-dev": { 2022 + "phpstan/phpstan": "1.10.39", 2023 + "phpunit/phpunit": "^9.6" 2024 + }, 2025 + "type": "library", 2026 + "autoload": { 2027 + "files": [ 2028 + "src/functions_include.php" 2029 + ], 2030 + "psr-4": { 2031 + "React\\Async\\": "src/" 2032 + } 2033 + }, 2034 + "notification-url": "https://packagist.org/downloads/", 2035 + "license": [ 2036 + "MIT" 2037 + ], 2038 + "authors": [ 2039 + { 2040 + "name": "Christian Lück", 2041 + "email": "christian@clue.engineering", 2042 + "homepage": "https://clue.engineering/" 2043 + }, 2044 + { 2045 + "name": "Cees-Jan Kiewiet", 2046 + "email": "reactphp@ceesjankiewiet.nl", 2047 + "homepage": "https://wyrihaximus.net/" 2048 + }, 2049 + { 2050 + "name": "Jan Sorgalla", 2051 + "email": "jsorgalla@gmail.com", 2052 + "homepage": "https://sorgalla.com/" 2053 + }, 2054 + { 2055 + "name": "Chris Boden", 2056 + "email": "cboden@gmail.com", 2057 + "homepage": "https://cboden.dev/" 2058 + } 2059 + ], 2060 + "description": "Async utilities and fibers for ReactPHP", 2061 + "keywords": [ 2062 + "async", 2063 + "reactphp" 2064 + ], 2065 + "support": { 2066 + "issues": "https://github.com/reactphp/async/issues", 2067 + "source": "https://github.com/reactphp/async/tree/v4.3.0" 2068 + }, 2069 + "funding": [ 2070 + { 2071 + "url": "https://opencollective.com/reactphp", 2072 + "type": "open_collective" 2073 + } 2074 + ], 2075 + "time": "2024-06-04T14:40:02+00:00" 2076 + }, 2077 + { 2078 + "name": "react/event-loop", 2079 + "version": "v1.5.0", 2080 + "source": { 2081 + "type": "git", 2082 + "url": "https://github.com/reactphp/event-loop.git", 2083 + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" 2084 + }, 2085 + "dist": { 2086 + "type": "zip", 2087 + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", 2088 + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", 2089 + "shasum": "" 2090 + }, 2091 + "require": { 2092 + "php": ">=5.3.0" 2093 + }, 2094 + "require-dev": { 2095 + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" 2096 + }, 2097 + "suggest": { 2098 + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" 2099 + }, 2100 + "type": "library", 2101 + "autoload": { 2102 + "psr-4": { 2103 + "React\\EventLoop\\": "src/" 2104 + } 2105 + }, 2106 + "notification-url": "https://packagist.org/downloads/", 2107 + "license": [ 2108 + "MIT" 2109 + ], 2110 + "authors": [ 2111 + { 2112 + "name": "Christian Lück", 2113 + "email": "christian@clue.engineering", 2114 + "homepage": "https://clue.engineering/" 2115 + }, 2116 + { 2117 + "name": "Cees-Jan Kiewiet", 2118 + "email": "reactphp@ceesjankiewiet.nl", 2119 + "homepage": "https://wyrihaximus.net/" 2120 + }, 2121 + { 2122 + "name": "Jan Sorgalla", 2123 + "email": "jsorgalla@gmail.com", 2124 + "homepage": "https://sorgalla.com/" 2125 + }, 2126 + { 2127 + "name": "Chris Boden", 2128 + "email": "cboden@gmail.com", 2129 + "homepage": "https://cboden.dev/" 2130 + } 2131 + ], 2132 + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", 2133 + "keywords": [ 2134 + "asynchronous", 2135 + "event-loop" 2136 + ], 2137 + "support": { 2138 + "issues": "https://github.com/reactphp/event-loop/issues", 2139 + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" 2140 + }, 2141 + "funding": [ 2142 + { 2143 + "url": "https://opencollective.com/reactphp", 2144 + "type": "open_collective" 2145 + } 2146 + ], 2147 + "time": "2023-11-13T13:48:05+00:00" 2148 + }, 2149 + { 2150 + "name": "react/promise", 2151 + "version": "v3.3.0", 2152 + "source": { 2153 + "type": "git", 2154 + "url": "https://github.com/reactphp/promise.git", 2155 + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" 2156 + }, 2157 + "dist": { 2158 + "type": "zip", 2159 + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", 2160 + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", 2161 + "shasum": "" 2162 + }, 2163 + "require": { 2164 + "php": ">=7.1.0" 2165 + }, 2166 + "require-dev": { 2167 + "phpstan/phpstan": "1.12.28 || 1.4.10", 2168 + "phpunit/phpunit": "^9.6 || ^7.5" 2169 + }, 2170 + "type": "library", 2171 + "autoload": { 2172 + "files": [ 2173 + "src/functions_include.php" 2174 + ], 2175 + "psr-4": { 2176 + "React\\Promise\\": "src/" 2177 + } 2178 + }, 2179 + "notification-url": "https://packagist.org/downloads/", 2180 + "license": [ 2181 + "MIT" 2182 + ], 2183 + "authors": [ 2184 + { 2185 + "name": "Jan Sorgalla", 2186 + "email": "jsorgalla@gmail.com", 2187 + "homepage": "https://sorgalla.com/" 2188 + }, 2189 + { 2190 + "name": "Christian Lück", 2191 + "email": "christian@clue.engineering", 2192 + "homepage": "https://clue.engineering/" 2193 + }, 2194 + { 2195 + "name": "Cees-Jan Kiewiet", 2196 + "email": "reactphp@ceesjankiewiet.nl", 2197 + "homepage": "https://wyrihaximus.net/" 2198 + }, 2199 + { 2200 + "name": "Chris Boden", 2201 + "email": "cboden@gmail.com", 2202 + "homepage": "https://cboden.dev/" 2203 + } 2204 + ], 2205 + "description": "A lightweight implementation of CommonJS Promises/A for PHP", 2206 + "keywords": [ 2207 + "promise", 2208 + "promises" 2209 + ], 2210 + "support": { 2211 + "issues": "https://github.com/reactphp/promise/issues", 2212 + "source": "https://github.com/reactphp/promise/tree/v3.3.0" 2213 + }, 2214 + "funding": [ 2215 + { 2216 + "url": "https://opencollective.com/reactphp", 2217 + "type": "open_collective" 2218 + } 2219 + ], 2220 + "time": "2025-08-19T18:57:03+00:00" 2001 2221 }, 2002 2222 { 2003 2223 "name": "symfony/css-selector",
+1
css/_partials/_globals.scss
··· 83 83 [popover] { 84 84 max-height: calc(100% - 40px); 85 85 max-width: calc(100% - 40px); 86 + min-width: 280px; 86 87 display: none; 87 88 grid-template-rows: 1fr auto; 88 89 border: 1px var(--border-color) solid;
+2 -1
css/_partials/_popovers.scss
··· 7 7 } 8 8 9 9 #likes, 10 - #reposts { 10 + #reposts, 11 + #quotes { 11 12 .inner { 12 13 display: grid; 13 14 gap: 10px;
+8 -2
css/_partials/_post.scss
··· 1 - .post { 1 + div.post { 2 2 border-bottom: 1px var(--border-color) solid; 3 3 padding: 10px; 4 4 width: 100%; ··· 121 121 } 122 122 } 123 123 124 - .postInteraction { 124 + .actions, 125 + #interactions { 125 126 display: flex; 126 127 gap: 10px; 127 128 ··· 133 134 color: inherit; 134 135 background-color: transparent; 135 136 cursor: pointer; 137 + text-transform: lowercase; 136 138 } 139 + } 140 + 141 + #interactions { 142 + border-bottom: 1px var(--border-color) solid; 137 143 } 138 144 139 145 #replies {
+5 -1
css/_partials/_profile.scss
··· 20 20 aspect-ratio: 740/247; 21 21 margin-bottom: 25px; 22 22 23 - img { 23 + img, .nobanner { 24 24 width: 100%; 25 25 height: 100%; 26 26 object-fit: cover; 27 + } 28 + 29 + .nobanner { 30 + background-color: var(--btn-primary-bg); 27 31 } 28 32 } 29 33
+6 -1
css/_partials/_profileMini.scss
··· 1 1 .profileMini { 2 + min-width: 250px; 3 + padding: 10px; 4 + 2 5 a { 3 6 text-decoration: none; 4 7 color: inherit; 5 8 display: grid; 6 9 grid-template-areas: "avatar name"; 7 10 grid-template-columns: 48px 1fr; 8 - min-width: 250px; 9 11 gap: 10px; 10 12 } 11 13 ··· 16 18 17 19 img { 18 20 object-fit: cover; 21 + width: 100%; 22 + height: 100%; 23 + border-radius: 100%; 19 24 } 20 25 } 21 26
+98 -15
index.php
··· 1 1 <?php 2 + error_reporting(E_ALL); 3 + ini_set('display_errors', 'On'); 4 + 2 5 require_once('vendor/autoload.php'); 3 6 require_once('config.php'); 4 7 require_once('lib/bskyToucher.php'); 5 8 6 9 use Smallnest\Bsky\BskyToucher; 7 10 use League\CommonMark\CommonMarkConverter; 11 + use React\Promise\Deferred; 12 + use React\EventLoop\Loop; 13 + use React\Promise\Promise; 8 14 9 15 $bskyToucher = new BskyToucher(); 10 16 ··· 45 51 }); 46 52 47 53 Flight::route('/u/@handle:[a-z0-9\.]+/@rkey:[a-z0-9]+', function (string $handle, string $rkey): void { 54 + global $bskyToucher, $data; 48 55 $bskyToucher = new BskyToucher(); 49 - $post = $bskyToucher->getPost($handle, $rkey, Flight::get('userAuth') === null); 50 - $atUri = 'at://'.$post->did.'/app.bsky.feed.post/'.$rkey; 51 - $latte = new Latte\Engine; 52 - $latte->render('./templates/single.latte', array_merge(Flight::get('standardParams'), [ 53 - 'mainClass' => 'post', 54 - 'post' => $post, 55 - 'likes' => $bskyToucher->getLikeUsers($atUri), 56 - 'reposts' => $bskyToucher->getRepostUsers($atUri), 57 - 'quotes' => $bskyToucher->getQuoteRecords($atUri), 58 - 'replies' => $bskyToucher->getReplyRecords($atUri) 59 - ])); 56 + $deferred = new React\Promise\Deferred(); 57 + $data = []; 58 + $deferred->promise() 59 + ->then(function ($x) { 60 + global $bskyToucher, $data; 61 + $data = [ 62 + 'handle' => $x['handle'], 63 + 'post' => $bskyToucher->getPost($x['handle'], $x['rkey'], Flight::get('userAuth') === null), 64 + 'rkey' => $x['rkey'] 65 + ]; 66 + return [ 67 + 'handle' => $x['handle'], 68 + 'post' => $bskyToucher->getPost($x['handle'], $x['rkey'], Flight::get('userAuth') === null), 69 + 'rkey' => $x['rkey'] 70 + ]; 71 + }) 72 + ->then(function($x) { 73 + global $atUri; 74 + $atUri = 'at://'.$x['post']->did.'/app.bsky.feed.post/'.$x['rkey']; 75 + return React\Async\parallel([ 76 + function () { 77 + return new Promise(function ($resolve) { 78 + global $bskyToucher, $atUri; 79 + $resolve($bskyToucher->getLikeUsers($atUri)); 80 + }); 81 + }, 82 + function () { 83 + return new Promise(function ($resolve) { 84 + global $bskyToucher, $atUri; 85 + $resolve($bskyToucher->getRepostUsers($atUri)); 86 + }); 87 + }, 88 + function () { 89 + return new Promise(function ($resolve) { 90 + global $bskyToucher, $atUri; 91 + $resolve($bskyToucher->getQuoteRecords($atUri)); 92 + }); 93 + }, 94 + function () { 95 + return new Promise(function ($resolve) { 96 + global $bskyToucher, $atUri; 97 + $resolve($bskyToucher->getReplyRecords($atUri)); 98 + }); 99 + } 100 + ])->then(function (array $results) { 101 + global $x; 102 + $x['likes'] = $results[0]; 103 + $x['reposts'] = $results[1]; 104 + $x['quotes'] = $results[2]; 105 + $x['replies'] = $results[3]; 106 + return $x; 107 + }, function (Exception $e) { 108 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 109 + }); 110 + })->then(function($x) { 111 + global $data; 112 + $latte = new Latte\Engine; 113 + $latte->render('./templates/single.latte', array_merge(Flight::get('standardParams'), [ 114 + 'mainClass' => 'post', 115 + 'post' => $data['post'], 116 + 'likes' => $x['likes'], 117 + 'reposts' => $x['reposts'], 118 + 'quotes' => $x['quotes'], 119 + 'replies' => $x['replies'] 120 + ])); 121 + })->catch(function (Exception $e) { 122 + echo 'Exception: '.$e->getMessage().PHP_EOL; 123 + }); 124 + $deferred->resolve([ 125 + 'handle' => $handle, 126 + 'rkey' => $rkey 127 + ]); 60 128 }); 61 129 62 130 Flight::route('/u/@handle:[a-z0-9\.]+(/@tab:[a-z]+)', function (string $handle, ?string $tab): void { 131 + $deferred = new React\Promise\Deferred(); 132 + $deferred->promise() 133 + ->then(function($x) { 134 + $bskyToucher = new BskyToucher(); 135 + $user = $bskyToucher->getUserInfo($x); 136 + return $user; 137 + })->then(function($x) { 138 + $bskyToucher = new BskyToucher(); 139 + $posts = $bskyToucher->getUserPosts($x->handle); 140 + print_r($posts); 141 + return [ 142 + 'mainClass' => 'profile', 143 + 'handle' => $x->handle, 144 + 'posts' => $posts, 145 + 'user' => $x 146 + ]; 147 + }); 148 + $params = $deferred->resolve($handle); 63 149 $latte = new Latte\Engine; 64 - $latte->render('./templates/profile.latte', array_merge(Flight::get('standardParams'), [ 65 - 'handle' => $handle, 66 - 'mainClass' => 'profile' 67 - ])); 150 + $latte->render('./templates/profile.latte', array_merge(Flight::get('standardParams'), $params)); 68 151 }); 69 152 70 153 Flight::route('/f/@did:did:plc:[0-9a-z]+/@name:[a-z0-9\-\_]+', function (string $did, string $name): void {
+73 -53
lib/bskyToucher.php
··· 19 19 use Monolog\Logger; 20 20 use Monolog\Handler\StreamHandler; 21 21 use Fusonic\OpenGraph\Consumer; 22 + use React\Promise\Deferred; 23 + use React\EventLoop\Loop; 24 + use React\Promise\Promise; 22 25 23 26 global $log; 24 - $log = new Logger('bskyToucherLog'); 25 - $log->pushHandler(new StreamHandler(SITE_PATH.'/smallbird-social.log', Level::Warning)); 27 + //$log = new Logger('bskyToucherLog'); 28 + //$log->pushHandler(new StreamHandler(SITE_PATH.'/smallbird-social.log', Level::Warning)); 26 29 27 30 class BskyToucher { 28 31 private $bskyApiBase = 'https://public.api.bsky.app/xrpc/'; ··· 61 64 return 1000 * $retries; 62 65 } 63 66 ); 64 - $handler->push($retryMiddleware); 67 + //$handler->push($retryMiddleware); 65 68 $httpClient = new Client([ 66 69 'timeout' => $timeout, 67 - 'handler' => $handler 70 + 'handler' => $handler, 71 + 'http_errors' => false 68 72 ]); 69 73 if ($method === 'GET') { 70 74 $opts['headers'] = ["Content-Type" => "application/x-www-form-urlencoded"]; ··· 202 206 array_splice($split_str, $facet->end + $additions, 0, '</a>'); 203 207 $additions++; 204 208 } else if ($facet->type === "mention") { 205 - $profileLink = '/u/@'.$facet->handle; 209 + $profileLink = '/u/'.$facet->handle; 206 210 $replaceString = '<a href="'.$profileLink.'">@'.$facet->handle.'</a>'; 207 211 array_splice($split_str, $facet->start + $additions, $facet->end - $facet->start, $replaceString); 208 212 $additions += 1 - ($facet->end - $facet->start); ··· 220 224 221 225 /* USER INFO */ 222 226 223 - function getUserInfo($identifier) { 224 - $userData = $this->getUserDidAndProvider($identifier); 225 - $pdsData = $this->getPdsData($userData->pds, "com.atproto.repo.getRecord", [ 226 - "repo" => $userData->did, 227 - "collection" => "app.bsky.actor.profile", 228 - "rkey" => "self" 229 - ]); 230 - 231 - /*$profileData = $this->getPdsData($pds, "app.bsky.actor.getProfile", [ 232 - 'actor' => $did 233 - ]);*/ 234 - 235 - if ($pdsData) { 227 + function getUserInfo(string $identifier): object|bool { 228 + echo 'hello world'; 229 + print_r($identifier); 230 + $userData = $this->getSlingshotIdentityMiniDoc($identifier); 231 + print_r($userData); 232 + $did = $userData->did; 233 + $pds = $userData->pds; 234 + $userInfo = $this->getSlingshotData($did, 'app.bsky.actor.profile', 'self'); 235 + if ($userInfo) { 236 236 return (object) [ 237 - 'handle' => $handle, 238 - 'displayName' => $pdsData->value->displayName, 237 + 'handle' => $userData->handle, 238 + 'displayName' => $userInfo->value->displayName, 239 239 'did' => $did, 240 - 'description' => $pdsData->value->description, 241 - 'avatar' => property_exists($pdsData->value, 'avatar') ? $this->getMediaUrl($pds, $did, $pdsData->value->avatar->ref->{'$link'}) : null, 242 - 'banner' => property_exists($pdsData->value, 'banner') ? $this->getMediaUrl($pds, $did, $pdsData->value->banner->ref->{'$link'}) : null, 243 - 'pinnedPost' => property_exists($pdsData->value, 'pinnedPost') ? $pdsData->value->pinnedPost->uri : null, 244 - 'followers' => $this->getConstellationLinkData($did, 'app.bsky.graph.follow', 'subject') 240 + 'pds' => $pds, 241 + 'description' => $userInfo->value->description, 242 + 'avatar' => property_exists($userInfo->value, 'avatar') ? $this->getMediaUrl($pds, $did, $userInfo->value->avatar->ref->{'$link'}) : null, 243 + 'banner' => property_exists($userInfo->value, 'banner') ? $this->getMediaUrl($pds, $did, $userInfo->value->banner->ref->{'$link'}) : null, 244 + 'pinnedPost' => property_exists($userInfo->value, 'pinnedPost') ? $userInfo->value->pinnedPost->uri : null 245 245 ]; 246 246 } 247 247 return false; ··· 281 281 282 282 /* POSTS */ 283 283 284 - function getPost(string $did, string $rkey, $slingshot = false):object|bool { 284 + function getPost(string $identifier, string $rkey, $slingshot = false):object|bool { 285 285 if (!$slingshot) { 286 286 return false; 287 287 } 288 - $ret = $this->getSlingshotData($did, "app.bsky.feed.post", $rkey); 289 - $userInfo = $this->getSlingshotData($did, "app.bsky.actor.profile", "self"); 288 + $ret = $this->getSlingshotData($identifier, "app.bsky.feed.post", $rkey); 289 + $userInfo = $this->getSlingshotData($identifier, "app.bsky.actor.profile", "self"); 290 + $uriComponents = $this->splitAtUri($userInfo->uri); 291 + $did = $uriComponents->did; 290 292 $plcData = $this->getPlcInfoFromRecord($did); 291 293 $author = (object) [ 292 294 'displayName' => $userInfo->value->displayName, ··· 368 370 return []; 369 371 } 370 372 371 - function getUserPosts(string $did, $auth = false, $cursor = null, $newer = false):array { 372 - $userData = $this->getSlingshotIdentityMiniDoc($did, 'app.bsky.actor.profile', 'self'); 373 - $postData = []; 373 + function getUserPosts(string $did, $auth = false, $cursor = null, $newer = false):array|bool { 374 + $userData = $this->getSlingshotIdentityMiniDoc($did); 375 + $postData = $this->getPdsData($userData->pds, 'com.atproto.repo.listRecords', [ 376 + 'repo' => $did, 377 + 'collection' => 'app.bsky.feed.post', 378 + 'cursor' => $cursor 379 + ]); 380 + 381 + if (!$postData) return false; 382 + $deferred = new \React\Promise\Deferred(); 383 + $deferred->promise() 384 + ->then(function($x) { 385 + React\Async\parallel(array_map(function($p) { 386 + return function () { 387 + global $p; 388 + return new Promise(function ($resolve) { 389 + global $p; 390 + $bskyToucher = new BskyToucher; 391 + $resolve($bskyToucher->sanitizePost($p)); 392 + }); 393 + }; 394 + }, $x)) 395 + ->then(function (array $results) { 396 + return $results; 397 + })->catch(function (Exception $e) { 398 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 399 + }); 400 + }, function (Exception $e) { 401 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 402 + }); 374 403 375 - if ($auth) { 376 - $postData = $this->getPdsData($userData->pds, 'com.atproto.repo.listRecords', [ 377 - 'repo' => $did, 378 - 'collection' => 'app.bsky.feed.post', 379 - 'limit' => 100, 380 - 'reverse' => true, 381 - 'cursor' => $cursor 382 - ]); 383 - } else { 384 - $postData = $this->getPublicApiData('app.bsky.feed.getAuthorFeed', [ 385 - 'actor' => $did 386 - ]); 387 - $postData = array_map(function ($post) { 388 - return $this->sanitizePost($post->post); 389 - }, $postData->feed); 390 - } 404 + $postData = $deferred->resolve($postData); 405 + 406 + if (!$postData) return false; 391 407 392 408 return $postData; 393 409 } ··· 468 484 $ret = (object) [ 469 485 'displayName' => property_exists($authorInfo->value, 'displayName') && $authorInfo->value->displayName !== "" ? $authorInfo->value->displayName : $authorInfo->handle, 470 486 'handle' => $authorData->handle, 471 - 'profileLink' => '/u/@'.$authorData->handle, 487 + 'profileLink' => '/u/'.$authorData->handle, 472 488 'avatar' => property_exists($authorInfo->value, 'avatar') ? $this->getMediaUrl($authorData->pds, $did, $authorInfo->value->avatar->ref->{'$link'}) : null, 473 489 'did' => $did, 474 490 'uri' => $post->uri, 475 491 'pds' => $authorData->pds, 476 492 'postId' => $rkey, 477 - 'postLink' => '/u/@'.$authorData->handle.'/'.$rkey, 493 + 'postLink' => '/u/'.$authorData->handle.'/'.$rkey, 478 494 'content' => $this->applyFacets($post->value->text, $facets), 479 495 'replyCount' => $this->getReplies($post->uri), 480 496 'repostCount' => $this->getReposts($post->uri), ··· 497 513 'displayName' => property_exists($post->author, 'displayName') && $post->author->displayName !== "" ? $post->author->displayName : $post->author->handle, 498 514 'handle' => $authorData->handle, 499 515 'did' => $authorData->did, 500 - 'profileLink' => '/u/@'.$authorData->handle, 516 + 'profileLink' => '/u/'.$authorData->handle, 501 517 'avatar' => $post->author->avatar, 502 518 'banner' => property_exists($post->author, 'banner') ? $post->author->banner : null, 503 519 'uri' => $post->uri, 504 520 'pds' => $authorData->pds, 505 521 'postId' => $rkeyMatch[2], 506 - 'postLink' => '/u/@'.$authorData->handle.'/'.$rkeyMatch[2], 522 + 'postLink' => '/u/'.$authorData->handle.'/'.$rkeyMatch[2], 507 523 'content' => $this->applyFacets($post->record->text, $facets), 508 524 'replyCount' => $post->replyCount, 509 525 'repostCount' => $post->repostCount, ··· 550 566 } else if ($embeds->{'$type'} === 'app.bsky.embed.record') { 551 567 $uriComponents = $this->splitAtUri($embeds->record->uri); 552 568 $post = $this->getSlingshotData($uriComponents->did, $uriComponents->collection, $uriComponents->rkey); 569 + $sanitizedPost = $this->sanitizePost($post, true); 553 570 return [ 554 571 (object) [ 555 - 'post' => $this->sanitizePost($post, true) 572 + 'post' => $sanitizedPost 556 573 ] 557 574 ]; 558 575 } else if ($embeds->{'$type'} === 'app.bsky.something.external') { ··· 590 607 } 591 608 592 609 function sanitizeUserList(array $users):array { 593 - return array_map(function ($rec) { 610 + $normalized = array_map(function ($rec) { 594 611 $hydratedRec = $rec; 595 612 $actorSlingshot = $this->getSlingshotData($rec->did, 'app.bsky.actor.profile', 'self'); 613 + if (!$actorSlingshot) return false; 596 614 $actorData = $this->getPlcInfoFromRecord($rec->did); 597 615 $hydratedRec->actor = (object) [ 598 616 'displayName' => $actorSlingshot->value->displayName !== "" ? htmlspecialchars($actorSlingshot->value->displayName) : $actorData->handle, ··· 602 620 ]; 603 621 return $hydratedRec; 604 622 }, $users); 623 + $ret = array_values(array_filter($normalized)); 624 + return $ret; 605 625 } 606 626 607 627 function sanitizeFacets(array $facets):array {
+10 -5
templates/_partials/embed_record.latte
··· 1 1 <div class="embeds"> 2 - <div class="record"> 3 - <a href="{$embed->uri}"> 4 - idk 5 - </a> 2 + <div class="record post"> 3 + <div class="postHeader"> 4 + <div class="avatar"> 5 + <a href="{$embed->profileLink}"><img src="{$embed->avatar}" alt="{$embed->displayName}'s user icon" /></a> 6 + </div> 7 + <div class="displayName"><a href="{$embed->profileLink}">{$embed->displayName}</a></div> 8 + <div class="handle"><a href="{$embed->profileLink}">{$embed->handle}</a></div> 9 + <div class="timeAgo"><a href="{$embed->postLink}">{$embed->createdAt}</a></div> 10 + </div> 6 11 </div> 7 - </div> 12 + </div>
+4 -4
templates/_partials/post.latte
··· 1 - <div class="post" data-uri="{$post->uri}"> 1 + <div class="post"> 2 2 <div class="postHeader"> 3 3 <div class="avatar"> 4 4 <a href="{$post->profileLink}"><img src="{$post->avatar}" alt="{$post->displayName}'s user icon" /></a> 5 5 </div> 6 - <div class="displayName"><a href="{$post->profileLink}">{$post->displayName}</a></div> 6 + <div class="displayName"><a href="{$post->profileLink}">{$post->displayName|noescape}</a></div> 7 7 <div class="handle"><a href="{$post->profileLink}">{$post->handle}</a></div> 8 8 <div class="timeAgo"><a href="{$post->postLink}">{$post->createdAt}</a></div> 9 9 </div> ··· 12 12 {include 'embed_image.latte', embeds: $post->embeds, postId: $post->postId} 13 13 {elseif $post->embedType === 'app.bsky.embed.video'} 14 14 {include 'embed_video.latte', embed: $post->embeds[0]} 15 - {elseif $post->embedType === 'app.bsky.embed.link'} 15 + {elseif $post->embedType === 'app.bsky.embed.external'} 16 16 {include 'embed_link.latte', embed: $post->embeds[0]} 17 17 {elseif $post->embedType === 'app.bsky.embed.record'} 18 18 {include 'embed_record.latte', embed: $post->embeds[0]} 19 19 {/if} 20 20 <div class="postFooter"> 21 - <div class="postInteraction"> 21 + <div class="actions"> 22 22 <button type="button" data-action="like">like ({$post->likeCount})</button> 23 23 <button type="button" data-action="like">reply ({$post->replyCount})</button> 24 24 <button type="button" data-action="quote">quote ({$post->quoteCount})</button>
+5 -5
templates/_partials/profile_mini.latte
··· 1 1 <div class="profileMini"> 2 - <a href="{$user->profileLink}"> 2 + <a href="{$profileLink}"> 3 3 <div class="avatar"> 4 - <img src="$user->avatar" alt="" /> 4 + <img src="{$avatar}" alt="" /> 5 5 </div> 6 6 <div class="name"> 7 - <span class="displayName">{$user->displayName}</span> 8 - <span class="handle">{$user->handle}</span> 9 - <span class="did">{$user->did}</span> 7 + <span class="displayName">{$displayName}</span> 8 + <span class="handle">{$handle}</span> 9 + <span class="did">{$did}</span> 10 10 </div> 11 11 </a> 12 12 </div>
+31 -26
templates/profile.latte
··· 1 - <div class="profileCard"> 2 - <div class="banner"> 3 - <img src="{$banner}" alt="" /> 4 - </div> 5 - <div class="avatar"> 6 - <img src="{$avatar}" alt="" /> 7 - </div> 8 - <div class="profileInfo"> 9 - <div class="displayName">{$displayName}</div> 10 - <div class="handle">{$handle}</div> 11 - <div class="did">{$did}</div> 12 - <div class="description"> 13 - {$description} 1 + {layout 'layout.latte'} 2 + 3 + {block title} | {$user->displayName} (@{$user->handle}){/block} 4 + 5 + {block content} 6 + <!-- 7 + {print_r($user)|noescape} 8 + --> 9 + <div class="profileCard"> 10 + <div class="banner"> 11 + <img src="{$user->banner}" alt="" n:if="$user->banner" /> 12 + <div class="nobanner" n:else></div> 14 13 </div> 15 - <div class="userStats"> 16 - [following/followers/posts: to be implemented] 14 + <div class="avatar"> 15 + <img src="{$user->avatar}" alt="" /> 17 16 </div> 18 - <div class="actions"> 19 - <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> 17 + <div class="profileInfo"> 18 + <div class="displayName">{$user->displayName}</div> 19 + <div class="handle">@{$user->handle}</div> 20 + <div class="did">{$user->did}</div> 21 + <div class="description"> 22 + {$user->description} 23 + </div> 24 + <div class="userStats"> 25 + </div> 26 + <div class="actions"> 27 + <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> 28 + </div> 20 29 </div> 21 30 </div> 22 - </div> 23 - <div class="postsList"> 24 - <a href="#" class="refreshBar" data-feed="author">check for new posts</a> 25 - {if $posts} 26 - {foreach $posts as $post} 27 - {include 'post.latte', post: $post} 28 - {/foreach} 29 - {/if} 30 - </div> 31 + <div class="postsList"> 32 + <a href="#" class="refreshBar" data-feed="author">check for new posts</a> 33 + {include '_partials/feedPosts.latte', posts: $posts} 34 + </div> 35 + {/block}
+18 -12
templates/search.latte
··· 1 - <form class="searchForm" method="POST"> 2 - <div class="form-input"> 3 - <label>Query</label> 4 - <p class="help">Search is limited to this instance's flock of PDSes!</p> 5 - <input type="text" name="search" id="search" /> 6 - </div> 7 - <div class="form-input btn-row"> 8 - <button type="submit" class="btn btn-primary">Search</button> 9 - </div> 10 - </form> 11 - <div id="results"> 1 + {layout 'layout.latte'} 2 + 3 + {block title} | search{/block} 12 4 13 - </div> 5 + {block content} 6 + <form class="searchForm" method="POST"> 7 + <div class="form-input"> 8 + <label>Query</label> 9 + <p class="help">Search is limited to this instance's flock of PDSes!</p> 10 + <input type="text" name="search" id="search" /> 11 + </div> 12 + <div class="form-input btn-row"> 13 + <button type="submit" class="btn btn-primary">Search</button> 14 + </div> 15 + </form> 16 + <div id="results"> 17 + 18 + </div> 19 + {/block}
+2
templates/settings.latte
··· 1 1 {layout 'layout.latte'} 2 2 3 + {block title} | settings{/block} 4 + 3 5 {block content} 4 6 <h2>Settings</h2> 5 7 At some point settings will go here!
+33 -13
templates/single.latte
··· 1 1 {layout 'layout.latte'} 2 2 3 - {include '_partials/post.latte', post: $post} 3 + {block title} | post by {$post->displayName} (@{$post->handle}){/block} 4 + 5 + {block content} 6 + {include '_partials/post.latte', post: $post} 4 7 <div id="interactions"> 5 8 <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> 6 9 </div> 7 10 <div id="likes" popover> 8 - {foreach $post->likes as $like} 9 - {include 'profile_mini.latte', user: $like} 10 - {/foreach} 11 + <div class="inner"> 12 + <div n:if="count($likes) === 0"> 13 + <p>No reposts yet!</p> 14 + </div> 15 + {foreach $likes as $like} 16 + {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} 17 + {/foreach} 18 + </div> 11 19 </div> 12 20 <div id="reposts" popover> 13 - {foreach $post->reposts as $repost} 14 - {include 'profile_mini.latte', user: $repost} 15 - {/foreach} 21 + <div class="inner"> 22 + <div n:if="count($reposts) === 0"> 23 + <p>No reposts yet!</p> 24 + </div> 25 + {foreach $reposts as $repost} 26 + {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} 27 + {/foreach} 28 + </div> 16 29 </div> 17 30 <div id="quotes" popover> 18 - {foreach $post->quotes as $quote} 19 - {include 'post.latte', post: $quote} 20 - {/foreach} 31 + <div class="inner"> 32 + <div n:if="count($quotes) === 0"> 33 + <p>No quotes yet!</p> 34 + </div> 35 + {foreach $quotes as $quote} 36 + {include '_partials/post.latte', post: $quote} 37 + {/foreach} 38 + </div> 21 39 </div> 22 40 <div id="replies"> 23 - {foreach $post->replies as $reply} 24 - {include 'post.latte', post: $reply} 41 + <h2>Replies</h2> 42 + {foreach $replies as $reply} 43 + {include '_partials/post.latte', post: $reply} 25 44 {/foreach} 26 - </div> 45 + </div> 46 + {/block}
+2
vendor/composer/autoload_files.php
··· 12 12 '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', 13 13 '6594cffae477af99d531f0d3d4c9e533' => $vendorDir . '/adhocore/cli/src/functions.php', 14 14 '4cdafd4a5191caf078235e7dd119fdaf' => $vendorDir . '/flightphp/core/flight/autoload.php', 15 + 'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php', 15 16 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', 16 17 'd507e002f7fce7f0c6dbf1f22edcb902' => $vendorDir . '/tracy/tracy/src/Tracy/functions.php', 17 18 '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', 19 + 'c4e03ecd470d2a87804979c0a8152284' => $vendorDir . '/react/async/src/functions_include.php', 18 20 );
+3
vendor/composer/autoload_psr4.php
··· 16 16 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), 17 17 'Symfony\\Component\\DomCrawler\\' => array($vendorDir . '/symfony/dom-crawler'), 18 18 'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'), 19 + 'React\\Promise\\' => array($vendorDir . '/react/promise/src'), 20 + 'React\\EventLoop\\' => array($vendorDir . '/react/event-loop/src'), 21 + 'React\\Async\\' => array($vendorDir . '/react/async/src'), 19 22 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), 20 23 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), 21 24 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
+20
vendor/composer/autoload_static.php
··· 13 13 '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', 14 14 '6594cffae477af99d531f0d3d4c9e533' => __DIR__ . '/..' . '/adhocore/cli/src/functions.php', 15 15 '4cdafd4a5191caf078235e7dd119fdaf' => __DIR__ . '/..' . '/flightphp/core/flight/autoload.php', 16 + 'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php', 16 17 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', 17 18 'd507e002f7fce7f0c6dbf1f22edcb902' => __DIR__ . '/..' . '/tracy/tracy/src/Tracy/functions.php', 18 19 '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', 20 + 'c4e03ecd470d2a87804979c0a8152284' => __DIR__ . '/..' . '/react/async/src/functions_include.php', 19 21 ); 20 22 21 23 public static $prefixLengthsPsr4 = array ( ··· 37 39 'Symfony\\Polyfill\\Ctype\\' => 23, 38 40 'Symfony\\Component\\DomCrawler\\' => 29, 39 41 'Symfony\\Component\\CssSelector\\' => 30, 42 + ), 43 + 'R' => 44 + array ( 45 + 'React\\Promise\\' => 14, 46 + 'React\\EventLoop\\' => 16, 47 + 'React\\Async\\' => 12, 40 48 ), 41 49 'P' => 42 50 array ( ··· 120 128 'Symfony\\Component\\CssSelector\\' => 121 129 array ( 122 130 0 => __DIR__ . '/..' . '/symfony/css-selector', 131 + ), 132 + 'React\\Promise\\' => 133 + array ( 134 + 0 => __DIR__ . '/..' . '/react/promise/src', 135 + ), 136 + 'React\\EventLoop\\' => 137 + array ( 138 + 0 => __DIR__ . '/..' . '/react/event-loop/src', 139 + ), 140 + 'React\\Async\\' => 141 + array ( 142 + 0 => __DIR__ . '/..' . '/react/async/src', 123 143 ), 124 144 'Psr\\Log\\' => 125 145 array (
+229
vendor/composer/installed.json
··· 2123 2123 "install-path": "../ralouphie/getallheaders" 2124 2124 }, 2125 2125 { 2126 + "name": "react/async", 2127 + "version": "v4.3.0", 2128 + "version_normalized": "4.3.0.0", 2129 + "source": { 2130 + "type": "git", 2131 + "url": "https://github.com/reactphp/async.git", 2132 + "reference": "635d50e30844a484495713e8cb8d9e079c0008a5" 2133 + }, 2134 + "dist": { 2135 + "type": "zip", 2136 + "url": "https://api.github.com/repos/reactphp/async/zipball/635d50e30844a484495713e8cb8d9e079c0008a5", 2137 + "reference": "635d50e30844a484495713e8cb8d9e079c0008a5", 2138 + "shasum": "" 2139 + }, 2140 + "require": { 2141 + "php": ">=8.1", 2142 + "react/event-loop": "^1.2", 2143 + "react/promise": "^3.2 || ^2.8 || ^1.2.1" 2144 + }, 2145 + "require-dev": { 2146 + "phpstan/phpstan": "1.10.39", 2147 + "phpunit/phpunit": "^9.6" 2148 + }, 2149 + "time": "2024-06-04T14:40:02+00:00", 2150 + "type": "library", 2151 + "installation-source": "dist", 2152 + "autoload": { 2153 + "files": [ 2154 + "src/functions_include.php" 2155 + ], 2156 + "psr-4": { 2157 + "React\\Async\\": "src/" 2158 + } 2159 + }, 2160 + "notification-url": "https://packagist.org/downloads/", 2161 + "license": [ 2162 + "MIT" 2163 + ], 2164 + "authors": [ 2165 + { 2166 + "name": "Christian Lück", 2167 + "email": "christian@clue.engineering", 2168 + "homepage": "https://clue.engineering/" 2169 + }, 2170 + { 2171 + "name": "Cees-Jan Kiewiet", 2172 + "email": "reactphp@ceesjankiewiet.nl", 2173 + "homepage": "https://wyrihaximus.net/" 2174 + }, 2175 + { 2176 + "name": "Jan Sorgalla", 2177 + "email": "jsorgalla@gmail.com", 2178 + "homepage": "https://sorgalla.com/" 2179 + }, 2180 + { 2181 + "name": "Chris Boden", 2182 + "email": "cboden@gmail.com", 2183 + "homepage": "https://cboden.dev/" 2184 + } 2185 + ], 2186 + "description": "Async utilities and fibers for ReactPHP", 2187 + "keywords": [ 2188 + "async", 2189 + "reactphp" 2190 + ], 2191 + "support": { 2192 + "issues": "https://github.com/reactphp/async/issues", 2193 + "source": "https://github.com/reactphp/async/tree/v4.3.0" 2194 + }, 2195 + "funding": [ 2196 + { 2197 + "url": "https://opencollective.com/reactphp", 2198 + "type": "open_collective" 2199 + } 2200 + ], 2201 + "install-path": "../react/async" 2202 + }, 2203 + { 2204 + "name": "react/event-loop", 2205 + "version": "v1.5.0", 2206 + "version_normalized": "1.5.0.0", 2207 + "source": { 2208 + "type": "git", 2209 + "url": "https://github.com/reactphp/event-loop.git", 2210 + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" 2211 + }, 2212 + "dist": { 2213 + "type": "zip", 2214 + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", 2215 + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", 2216 + "shasum": "" 2217 + }, 2218 + "require": { 2219 + "php": ">=5.3.0" 2220 + }, 2221 + "require-dev": { 2222 + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" 2223 + }, 2224 + "suggest": { 2225 + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" 2226 + }, 2227 + "time": "2023-11-13T13:48:05+00:00", 2228 + "type": "library", 2229 + "installation-source": "dist", 2230 + "autoload": { 2231 + "psr-4": { 2232 + "React\\EventLoop\\": "src/" 2233 + } 2234 + }, 2235 + "notification-url": "https://packagist.org/downloads/", 2236 + "license": [ 2237 + "MIT" 2238 + ], 2239 + "authors": [ 2240 + { 2241 + "name": "Christian Lück", 2242 + "email": "christian@clue.engineering", 2243 + "homepage": "https://clue.engineering/" 2244 + }, 2245 + { 2246 + "name": "Cees-Jan Kiewiet", 2247 + "email": "reactphp@ceesjankiewiet.nl", 2248 + "homepage": "https://wyrihaximus.net/" 2249 + }, 2250 + { 2251 + "name": "Jan Sorgalla", 2252 + "email": "jsorgalla@gmail.com", 2253 + "homepage": "https://sorgalla.com/" 2254 + }, 2255 + { 2256 + "name": "Chris Boden", 2257 + "email": "cboden@gmail.com", 2258 + "homepage": "https://cboden.dev/" 2259 + } 2260 + ], 2261 + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", 2262 + "keywords": [ 2263 + "asynchronous", 2264 + "event-loop" 2265 + ], 2266 + "support": { 2267 + "issues": "https://github.com/reactphp/event-loop/issues", 2268 + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" 2269 + }, 2270 + "funding": [ 2271 + { 2272 + "url": "https://opencollective.com/reactphp", 2273 + "type": "open_collective" 2274 + } 2275 + ], 2276 + "install-path": "../react/event-loop" 2277 + }, 2278 + { 2279 + "name": "react/promise", 2280 + "version": "v3.3.0", 2281 + "version_normalized": "3.3.0.0", 2282 + "source": { 2283 + "type": "git", 2284 + "url": "https://github.com/reactphp/promise.git", 2285 + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" 2286 + }, 2287 + "dist": { 2288 + "type": "zip", 2289 + "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", 2290 + "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", 2291 + "shasum": "" 2292 + }, 2293 + "require": { 2294 + "php": ">=7.1.0" 2295 + }, 2296 + "require-dev": { 2297 + "phpstan/phpstan": "1.12.28 || 1.4.10", 2298 + "phpunit/phpunit": "^9.6 || ^7.5" 2299 + }, 2300 + "time": "2025-08-19T18:57:03+00:00", 2301 + "type": "library", 2302 + "installation-source": "dist", 2303 + "autoload": { 2304 + "files": [ 2305 + "src/functions_include.php" 2306 + ], 2307 + "psr-4": { 2308 + "React\\Promise\\": "src/" 2309 + } 2310 + }, 2311 + "notification-url": "https://packagist.org/downloads/", 2312 + "license": [ 2313 + "MIT" 2314 + ], 2315 + "authors": [ 2316 + { 2317 + "name": "Jan Sorgalla", 2318 + "email": "jsorgalla@gmail.com", 2319 + "homepage": "https://sorgalla.com/" 2320 + }, 2321 + { 2322 + "name": "Christian Lück", 2323 + "email": "christian@clue.engineering", 2324 + "homepage": "https://clue.engineering/" 2325 + }, 2326 + { 2327 + "name": "Cees-Jan Kiewiet", 2328 + "email": "reactphp@ceesjankiewiet.nl", 2329 + "homepage": "https://wyrihaximus.net/" 2330 + }, 2331 + { 2332 + "name": "Chris Boden", 2333 + "email": "cboden@gmail.com", 2334 + "homepage": "https://cboden.dev/" 2335 + } 2336 + ], 2337 + "description": "A lightweight implementation of CommonJS Promises/A for PHP", 2338 + "keywords": [ 2339 + "promise", 2340 + "promises" 2341 + ], 2342 + "support": { 2343 + "issues": "https://github.com/reactphp/promise/issues", 2344 + "source": "https://github.com/reactphp/promise/tree/v3.3.0" 2345 + }, 2346 + "funding": [ 2347 + { 2348 + "url": "https://opencollective.com/reactphp", 2349 + "type": "open_collective" 2350 + } 2351 + ], 2352 + "install-path": "../react/promise" 2353 + }, 2354 + { 2126 2355 "name": "symfony/css-selector", 2127 2356 "version": "v7.3.0", 2128 2357 "version_normalized": "7.3.0.0",
+29 -2
vendor/composer/installed.php
··· 3 3 'name' => '__root__', 4 4 'pretty_version' => 'dev-main', 5 5 'version' => 'dev-main', 6 - 'reference' => '17a2b5d2e68ab0da5b500ef4ae0fc450b64a0b69', 6 + 'reference' => 'bea8476a6fd638d67046c875c6410c586ef4f117', 7 7 'type' => 'library', 8 8 'install_path' => __DIR__ . '/../../', 9 9 'aliases' => array(), ··· 13 13 '__root__' => array( 14 14 'pretty_version' => 'dev-main', 15 15 'version' => 'dev-main', 16 - 'reference' => '17a2b5d2e68ab0da5b500ef4ae0fc450b64a0b69', 16 + 'reference' => 'bea8476a6fd638d67046c875c6410c586ef4f117', 17 17 'type' => 'library', 18 18 'install_path' => __DIR__ . '/../../', 19 19 'aliases' => array(), ··· 298 298 'reference' => '120b605dfeb996808c31b6477290a714d356e822', 299 299 'type' => 'library', 300 300 'install_path' => __DIR__ . '/../ralouphie/getallheaders', 301 + 'aliases' => array(), 302 + 'dev_requirement' => false, 303 + ), 304 + 'react/async' => array( 305 + 'pretty_version' => 'v4.3.0', 306 + 'version' => '4.3.0.0', 307 + 'reference' => '635d50e30844a484495713e8cb8d9e079c0008a5', 308 + 'type' => 'library', 309 + 'install_path' => __DIR__ . '/../react/async', 310 + 'aliases' => array(), 311 + 'dev_requirement' => false, 312 + ), 313 + 'react/event-loop' => array( 314 + 'pretty_version' => 'v1.5.0', 315 + 'version' => '1.5.0.0', 316 + 'reference' => 'bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354', 317 + 'type' => 'library', 318 + 'install_path' => __DIR__ . '/../react/event-loop', 319 + 'aliases' => array(), 320 + 'dev_requirement' => false, 321 + ), 322 + 'react/promise' => array( 323 + 'pretty_version' => 'v3.3.0', 324 + 'version' => '3.3.0.0', 325 + 'reference' => '23444f53a813a3296c1368bb104793ce8d88f04a', 326 + 'type' => 'library', 327 + 'install_path' => __DIR__ . '/../react/promise', 301 328 'aliases' => array(), 302 329 'dev_requirement' => false, 303 330 ),
+112
vendor/react/async/CHANGELOG.md
··· 1 + # Changelog 2 + 3 + ## 4.3.0 (2024-06-04) 4 + 5 + * Feature: Improve performance by avoiding unneeded references in `FiberMap`. 6 + (#88 by @clue) 7 + 8 + * Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations. 9 + (#87 by @clue) 10 + 11 + * Improve type safety for test environment. 12 + (#86 by @SimonFrings) 13 + 14 + ## 4.2.0 (2023-11-22) 15 + 16 + * Feature: Add Promise v3 template types for all public functions. 17 + (#40 by @WyriHaximus and @clue) 18 + 19 + All our public APIs now use Promise v3 template types to guide IDEs and static 20 + analysis tools (like PHPStan), helping with proper type usage and improving 21 + code quality: 22 + 23 + ```php 24 + assertType('bool', await(resolve(true))); 25 + assertType('PromiseInterface<bool>', async(fn(): bool => true)()); 26 + assertType('PromiseInterface<bool>', coroutine(fn(): bool => true)); 27 + ``` 28 + 29 + * Feature: Full PHP 8.3 compatibility. 30 + (#81 by @clue) 31 + 32 + * Update test suite to avoid unhandled promise rejections. 33 + (#79 by @clue) 34 + 35 + ## 4.1.0 (2023-06-22) 36 + 37 + * Feature: Add new `delay()` function to delay program execution. 38 + (#69 and #78 by @clue) 39 + 40 + ```php 41 + echo 'a'; 42 + Loop::addTimer(1.0, function () { 43 + echo 'b'; 44 + }); 45 + React\Async\delay(3.0); 46 + echo 'c'; 47 + 48 + // prints "a" at t=0.0s 49 + // prints "b" at t=1.0s 50 + // prints "c" at t=3.0s 51 + ``` 52 + 53 + * Update test suite, add PHPStan with `max` level and report failed assertions. 54 + (#66 and #76 by @clue and #61 and #73 by @WyriHaximus) 55 + 56 + ## 4.0.0 (2022-07-11) 57 + 58 + A major new feature release, see [**release announcement**](https://clue.engineering/2022/announcing-reactphp-async). 59 + 60 + * We'd like to emphasize that this component is production ready and battle-tested. 61 + We plan to support all long-term support (LTS) releases for at least 24 months, 62 + so you have a rock-solid foundation to build on top of. 63 + 64 + * The v4 release will be the way forward for this package. However, we will still 65 + actively support v3 and v2 to provide a smooth upgrade path for those not yet 66 + on PHP 8.1+. If you're using an older PHP version, you may use either version 67 + which all provide a compatible API but may not take advantage of newer language 68 + features. You may target multiple versions at the same time to support a wider range of 69 + PHP versions: 70 + 71 + * [`4.x` branch](https://github.com/reactphp/async/tree/4.x) (PHP 8.1+) 72 + * [`3.x` branch](https://github.com/reactphp/async/tree/3.x) (PHP 7.1+) 73 + * [`2.x` branch](https://github.com/reactphp/async/tree/2.x) (PHP 5.3+) 74 + 75 + This update involves some major new features and a minor BC break over the 76 + `v3.0.0` release. We've tried hard to avoid BC breaks where possible and 77 + minimize impact otherwise. We expect that most consumers of this package will be 78 + affected by BC breaks, but updating should take no longer than a few minutes. 79 + See below for more details: 80 + 81 + * Feature / BC break: Require PHP 8.1+ and add `mixed` type declarations. 82 + (#14 by @clue) 83 + 84 + * Feature: Add Fiber-based `async()` and `await()` functions. 85 + (#15, #18, #19 and #20 by @WyriHaximus and #26, #28, #30, #32, #34, #55 and #57 by @clue) 86 + 87 + * Project maintenance, rename `main` branch to `4.x` and update installation instructions. 88 + (#29 by @clue) 89 + 90 + The following changes had to be ported to this release due to our branching 91 + strategy, but also appeared in the `v3.0.0` release: 92 + 93 + * Feature: Support iterable type for `parallel()` + `series()` + `waterfall()`. 94 + (#49 by @clue) 95 + 96 + * Feature: Forward compatibility with upcoming Promise v3. 97 + (#48 by @clue) 98 + 99 + * Minor documentation improvements. 100 + (#36 by @SimonFrings and #51 by @nhedger) 101 + 102 + ## 3.0.0 (2022-07-11) 103 + 104 + See [`3.x` CHANGELOG](https://github.com/reactphp/async/blob/3.x/CHANGELOG.md) for more details. 105 + 106 + ## 2.0.0 (2022-07-11) 107 + 108 + See [`2.x` CHANGELOG](https://github.com/reactphp/async/blob/2.x/CHANGELOG.md) for more details. 109 + 110 + ## 1.0.0 (2013-02-07) 111 + 112 + * First tagged release
+19
vendor/react/async/LICENSE
··· 1 + Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler 2 + 3 + Permission is hereby granted, free of charge, to any person obtaining a copy 4 + of this software and associated documentation files (the "Software"), to deal 5 + in the Software without restriction, including without limitation the rights 6 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 + copies of the Software, and to permit persons to whom the Software is furnished 8 + to do so, subject to the following conditions: 9 + 10 + The above copyright notice and this permission notice shall be included in all 11 + copies or substantial portions of the Software. 12 + 13 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 + THE SOFTWARE.
+672
vendor/react/async/README.md
··· 1 + # Async Utilities 2 + 3 + [![CI status](https://github.com/reactphp/async/workflows/CI/badge.svg)](https://github.com/reactphp/async/actions) 4 + [![installs on Packagist](https://img.shields.io/packagist/dt/react/async?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/async) 5 + 6 + Async utilities and fibers for [ReactPHP](https://reactphp.org/). 7 + 8 + This library allows you to manage async control flow. It provides a number of 9 + combinators for [Promise](https://github.com/reactphp/promise)-based APIs. 10 + Instead of nesting or chaining promise callbacks, you can declare them as a 11 + list, which is resolved sequentially in an async manner. 12 + React/Async will not automagically change blocking code to be async. You need 13 + to have an actual event loop and non-blocking libraries interacting with that 14 + event loop for it to work. As long as you have a Promise-based API that runs in 15 + an event loop, it can be used with this library. 16 + 17 + **Table of Contents** 18 + 19 + * [Usage](#usage) 20 + * [async()](#async) 21 + * [await()](#await) 22 + * [coroutine()](#coroutine) 23 + * [delay()](#delay) 24 + * [parallel()](#parallel) 25 + * [series()](#series) 26 + * [waterfall()](#waterfall) 27 + * [Todo](#todo) 28 + * [Install](#install) 29 + * [Tests](#tests) 30 + * [License](#license) 31 + 32 + ## Usage 33 + 34 + This lightweight library consists only of a few simple functions. 35 + All functions reside under the `React\Async` namespace. 36 + 37 + The below examples refer to all functions with their fully-qualified names like this: 38 + 39 + ```php 40 + React\Async\await(…); 41 + ``` 42 + 43 + As of PHP 5.6+ you can also import each required function into your code like this: 44 + 45 + ```php 46 + use function React\Async\await; 47 + 48 + await(…); 49 + ``` 50 + 51 + Alternatively, you can also use an import statement similar to this: 52 + 53 + ```php 54 + use React\Async; 55 + 56 + Async\await(…); 57 + ``` 58 + 59 + ### async() 60 + 61 + The `async(callable():(PromiseInterface<T>|T) $function): (callable():PromiseInterface<T>)` function can be used to 62 + return an async function for a function that uses [`await()`](#await) internally. 63 + 64 + This function is specifically designed to complement the [`await()` function](#await). 65 + The [`await()` function](#await) can be considered *blocking* from the 66 + perspective of the calling code. You can avoid this blocking behavior by 67 + wrapping it in an `async()` function call. Everything inside this function 68 + will still be blocked, but everything outside this function can be executed 69 + asynchronously without blocking: 70 + 71 + ```php 72 + Loop::addTimer(0.5, React\Async\async(function () { 73 + echo 'a'; 74 + React\Async\await(React\Promise\Timer\sleep(1.0)); 75 + echo 'c'; 76 + })); 77 + 78 + Loop::addTimer(1.0, function () { 79 + echo 'b'; 80 + }); 81 + 82 + // prints "a" at t=0.5s 83 + // prints "b" at t=1.0s 84 + // prints "c" at t=1.5s 85 + ``` 86 + 87 + See also the [`await()` function](#await) for more details. 88 + 89 + Note that this function only works in tandem with the [`await()` function](#await). 90 + In particular, this function does not "magically" make any blocking function 91 + non-blocking: 92 + 93 + ```php 94 + Loop::addTimer(0.5, React\Async\async(function () { 95 + echo 'a'; 96 + sleep(1); // broken: using PHP's blocking sleep() for demonstration purposes 97 + echo 'c'; 98 + })); 99 + 100 + Loop::addTimer(1.0, function () { 101 + echo 'b'; 102 + }); 103 + 104 + // prints "a" at t=0.5s 105 + // prints "c" at t=1.5s: Correct timing, but wrong order 106 + // prints "b" at t=1.5s: Triggered too late because it was blocked 107 + ``` 108 + 109 + As an alternative, you should always make sure to use this function in tandem 110 + with the [`await()` function](#await) and an async API returning a promise 111 + as shown in the previous example. 112 + 113 + The `async()` function is specifically designed for cases where it is used 114 + as a callback (such as an event loop timer, event listener, or promise 115 + callback). For this reason, it returns a new function wrapping the given 116 + `$function` instead of directly invoking it and returning its value. 117 + 118 + ```php 119 + use function React\Async\async; 120 + 121 + Loop::addTimer(1.0, async(function () { … })); 122 + $connection->on('close', async(function () { … })); 123 + $stream->on('data', async(function ($data) { … })); 124 + $promise->then(async(function (int $result) { … })); 125 + ``` 126 + 127 + You can invoke this wrapping function to invoke the given `$function` with 128 + any arguments given as-is. The function will always return a Promise which 129 + will be fulfilled with whatever your `$function` returns. Likewise, it will 130 + return a promise that will be rejected if you throw an `Exception` or 131 + `Throwable` from your `$function`. This allows you to easily create 132 + Promise-based functions: 133 + 134 + ```php 135 + $promise = React\Async\async(function (): int { 136 + $browser = new React\Http\Browser(); 137 + $urls = [ 138 + 'https://example.com/alice', 139 + 'https://example.com/bob' 140 + ]; 141 + 142 + $bytes = 0; 143 + foreach ($urls as $url) { 144 + $response = React\Async\await($browser->get($url)); 145 + assert($response instanceof Psr\Http\Message\ResponseInterface); 146 + $bytes += $response->getBody()->getSize(); 147 + } 148 + return $bytes; 149 + })(); 150 + 151 + $promise->then(function (int $bytes) { 152 + echo 'Total size: ' . $bytes . PHP_EOL; 153 + }, function (Exception $e) { 154 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 155 + }); 156 + ``` 157 + 158 + The previous example uses [`await()`](#await) inside a loop to highlight how 159 + this vastly simplifies consuming asynchronous operations. At the same time, 160 + this naive example does not leverage concurrent execution, as it will 161 + essentially "await" between each operation. In order to take advantage of 162 + concurrent execution within the given `$function`, you can "await" multiple 163 + promises by using a single [`await()`](#await) together with Promise-based 164 + primitives like this: 165 + 166 + ```php 167 + $promise = React\Async\async(function (): int { 168 + $browser = new React\Http\Browser(); 169 + $urls = [ 170 + 'https://example.com/alice', 171 + 'https://example.com/bob' 172 + ]; 173 + 174 + $promises = []; 175 + foreach ($urls as $url) { 176 + $promises[] = $browser->get($url); 177 + } 178 + 179 + try { 180 + $responses = React\Async\await(React\Promise\all($promises)); 181 + } catch (Exception $e) { 182 + foreach ($promises as $promise) { 183 + $promise->cancel(); 184 + } 185 + throw $e; 186 + } 187 + 188 + $bytes = 0; 189 + foreach ($responses as $response) { 190 + assert($response instanceof Psr\Http\Message\ResponseInterface); 191 + $bytes += $response->getBody()->getSize(); 192 + } 193 + return $bytes; 194 + })(); 195 + 196 + $promise->then(function (int $bytes) { 197 + echo 'Total size: ' . $bytes . PHP_EOL; 198 + }, function (Exception $e) { 199 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 200 + }); 201 + ``` 202 + 203 + The returned promise is implemented in such a way that it can be cancelled 204 + when it is still pending. Cancelling a pending promise will cancel any awaited 205 + promises inside that fiber or any nested fibers. As such, the following example 206 + will only output `ab` and cancel the pending [`delay()`](#delay). 207 + The [`await()`](#await) calls in this example would throw a `RuntimeException` 208 + from the cancelled [`delay()`](#delay) call that bubbles up through the fibers. 209 + 210 + ```php 211 + $promise = async(static function (): int { 212 + echo 'a'; 213 + await(async(static function (): void { 214 + echo 'b'; 215 + delay(2); 216 + echo 'c'; 217 + })()); 218 + echo 'd'; 219 + 220 + return time(); 221 + })(); 222 + 223 + $promise->cancel(); 224 + await($promise); 225 + ``` 226 + 227 + ### await() 228 + 229 + The `await(PromiseInterface<T> $promise): T` function can be used to 230 + block waiting for the given `$promise` to be fulfilled. 231 + 232 + ```php 233 + $result = React\Async\await($promise); 234 + ``` 235 + 236 + This function will only return after the given `$promise` has settled, i.e. 237 + either fulfilled or rejected. While the promise is pending, this function 238 + can be considered *blocking* from the perspective of the calling code. 239 + You can avoid this blocking behavior by wrapping it in an [`async()` function](#async) 240 + call. Everything inside this function will still be blocked, but everything 241 + outside this function can be executed asynchronously without blocking: 242 + 243 + ```php 244 + Loop::addTimer(0.5, React\Async\async(function () { 245 + echo 'a'; 246 + React\Async\await(React\Promise\Timer\sleep(1.0)); 247 + echo 'c'; 248 + })); 249 + 250 + Loop::addTimer(1.0, function () { 251 + echo 'b'; 252 + }); 253 + 254 + // prints "a" at t=0.5s 255 + // prints "b" at t=1.0s 256 + // prints "c" at t=1.5s 257 + ``` 258 + 259 + See also the [`async()` function](#async) for more details. 260 + 261 + Once the promise is fulfilled, this function will return whatever the promise 262 + resolved to. 263 + 264 + Once the promise is rejected, this will throw whatever the promise rejected 265 + with. If the promise did not reject with an `Exception` or `Throwable`, then 266 + this function will throw an `UnexpectedValueException` instead. 267 + 268 + ```php 269 + try { 270 + $result = React\Async\await($promise); 271 + // promise successfully fulfilled with $result 272 + echo 'Result: ' . $result; 273 + } catch (Throwable $e) { 274 + // promise rejected with $e 275 + echo 'Error: ' . $e->getMessage(); 276 + } 277 + ``` 278 + 279 + ### coroutine() 280 + 281 + The `coroutine(callable(mixed ...$args):(\Generator|PromiseInterface<T>|T) $function, mixed ...$args): PromiseInterface<T>` function can be used to 282 + execute a Generator-based coroutine to "await" promises. 283 + 284 + ```php 285 + React\Async\coroutine(function () { 286 + $browser = new React\Http\Browser(); 287 + 288 + try { 289 + $response = yield $browser->get('https://example.com/'); 290 + assert($response instanceof Psr\Http\Message\ResponseInterface); 291 + echo $response->getBody(); 292 + } catch (Exception $e) { 293 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 294 + } 295 + }); 296 + ``` 297 + 298 + Using Generator-based coroutines is an alternative to directly using the 299 + underlying promise APIs. For many use cases, this makes using promise-based 300 + APIs much simpler, as it resembles a synchronous code flow more closely. 301 + The above example performs the equivalent of directly using the promise APIs: 302 + 303 + ```php 304 + $browser = new React\Http\Browser(); 305 + 306 + $browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) { 307 + echo $response->getBody(); 308 + }, function (Exception $e) { 309 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 310 + }); 311 + ``` 312 + 313 + The `yield` keyword can be used to "await" a promise resolution. Internally, 314 + it will turn the entire given `$function` into a [`Generator`](https://www.php.net/manual/en/class.generator.php). 315 + This allows the execution to be interrupted and resumed at the same place 316 + when the promise is fulfilled. The `yield` statement returns whatever the 317 + promise is fulfilled with. If the promise is rejected, it will throw an 318 + `Exception` or `Throwable`. 319 + 320 + The `coroutine()` function will always return a Promise which will be 321 + fulfilled with whatever your `$function` returns. Likewise, it will return 322 + a promise that will be rejected if you throw an `Exception` or `Throwable` 323 + from your `$function`. This allows you to easily create Promise-based 324 + functions: 325 + 326 + ```php 327 + $promise = React\Async\coroutine(function () { 328 + $browser = new React\Http\Browser(); 329 + $urls = [ 330 + 'https://example.com/alice', 331 + 'https://example.com/bob' 332 + ]; 333 + 334 + $bytes = 0; 335 + foreach ($urls as $url) { 336 + $response = yield $browser->get($url); 337 + assert($response instanceof Psr\Http\Message\ResponseInterface); 338 + $bytes += $response->getBody()->getSize(); 339 + } 340 + return $bytes; 341 + }); 342 + 343 + $promise->then(function (int $bytes) { 344 + echo 'Total size: ' . $bytes . PHP_EOL; 345 + }, function (Exception $e) { 346 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 347 + }); 348 + ``` 349 + 350 + The previous example uses a `yield` statement inside a loop to highlight how 351 + this vastly simplifies consuming asynchronous operations. At the same time, 352 + this naive example does not leverage concurrent execution, as it will 353 + essentially "await" between each operation. In order to take advantage of 354 + concurrent execution within the given `$function`, you can "await" multiple 355 + promises by using a single `yield` together with Promise-based primitives 356 + like this: 357 + 358 + ```php 359 + $promise = React\Async\coroutine(function () { 360 + $browser = new React\Http\Browser(); 361 + $urls = [ 362 + 'https://example.com/alice', 363 + 'https://example.com/bob' 364 + ]; 365 + 366 + $promises = []; 367 + foreach ($urls as $url) { 368 + $promises[] = $browser->get($url); 369 + } 370 + 371 + try { 372 + $responses = yield React\Promise\all($promises); 373 + } catch (Exception $e) { 374 + foreach ($promises as $promise) { 375 + $promise->cancel(); 376 + } 377 + throw $e; 378 + } 379 + 380 + $bytes = 0; 381 + foreach ($responses as $response) { 382 + assert($response instanceof Psr\Http\Message\ResponseInterface); 383 + $bytes += $response->getBody()->getSize(); 384 + } 385 + return $bytes; 386 + }); 387 + 388 + $promise->then(function (int $bytes) { 389 + echo 'Total size: ' . $bytes . PHP_EOL; 390 + }, function (Exception $e) { 391 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 392 + }); 393 + ``` 394 + 395 + ### delay() 396 + 397 + The `delay(float $seconds): void` function can be used to 398 + delay program execution for duration given in `$seconds`. 399 + 400 + ```php 401 + React\Async\delay($seconds); 402 + ``` 403 + 404 + This function will only return after the given number of `$seconds` have 405 + elapsed. If there are no other events attached to this loop, it will behave 406 + similar to PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php). 407 + 408 + ```php 409 + echo 'a'; 410 + React\Async\delay(1.0); 411 + echo 'b'; 412 + 413 + // prints "a" at t=0.0s 414 + // prints "b" at t=1.0s 415 + ``` 416 + 417 + Unlike PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php), 418 + this function may not necessarily halt execution of the entire process thread. 419 + Instead, it allows the event loop to run any other events attached to the 420 + same loop until the delay returns: 421 + 422 + ```php 423 + echo 'a'; 424 + Loop::addTimer(1.0, function (): void { 425 + echo 'b'; 426 + }); 427 + React\Async\delay(3.0); 428 + echo 'c'; 429 + 430 + // prints "a" at t=0.0s 431 + // prints "b" at t=1.0s 432 + // prints "c" at t=3.0s 433 + ``` 434 + 435 + This behavior is especially useful if you want to delay the program execution 436 + of a particular routine, such as when building a simple polling or retry 437 + mechanism: 438 + 439 + ```php 440 + try { 441 + something(); 442 + } catch (Throwable) { 443 + // in case of error, retry after a short delay 444 + React\Async\delay(1.0); 445 + something(); 446 + } 447 + ``` 448 + 449 + Because this function only returns after some time has passed, it can be 450 + considered *blocking* from the perspective of the calling code. You can avoid 451 + this blocking behavior by wrapping it in an [`async()` function](#async) call. 452 + Everything inside this function will still be blocked, but everything outside 453 + this function can be executed asynchronously without blocking: 454 + 455 + ```php 456 + Loop::addTimer(0.5, React\Async\async(function (): void { 457 + echo 'a'; 458 + React\Async\delay(1.0); 459 + echo 'c'; 460 + })); 461 + 462 + Loop::addTimer(1.0, function (): void { 463 + echo 'b'; 464 + }); 465 + 466 + // prints "a" at t=0.5s 467 + // prints "b" at t=1.0s 468 + // prints "c" at t=1.5s 469 + ``` 470 + 471 + See also the [`async()` function](#async) for more details. 472 + 473 + Internally, the `$seconds` argument will be used as a timer for the loop so that 474 + it keeps running until this timer triggers. This implies that if you pass a 475 + really small (or negative) value, it will still start a timer and will thus 476 + trigger at the earliest possible time in the future. 477 + 478 + The function is implemented in such a way that it can be cancelled when it is 479 + running inside an [`async()` function](#async). Cancelling the resulting 480 + promise will clean up any pending timers and throw a `RuntimeException` from 481 + the pending delay which in turn would reject the resulting promise. 482 + 483 + ```php 484 + $promise = async(function (): void { 485 + echo 'a'; 486 + delay(3.0); 487 + echo 'b'; 488 + })(); 489 + 490 + Loop::addTimer(2.0, function () use ($promise): void { 491 + $promise->cancel(); 492 + }); 493 + 494 + // prints "a" at t=0.0s 495 + // rejects $promise at t=2.0 496 + // never prints "b" 497 + ``` 498 + 499 + ### parallel() 500 + 501 + The `parallel(iterable<callable():PromiseInterface<T>> $tasks): PromiseInterface<array<T>>` function can be used 502 + like this: 503 + 504 + ```php 505 + <?php 506 + 507 + use React\EventLoop\Loop; 508 + use React\Promise\Promise; 509 + 510 + React\Async\parallel([ 511 + function () { 512 + return new Promise(function ($resolve) { 513 + Loop::addTimer(1, function () use ($resolve) { 514 + $resolve('Slept for a whole second'); 515 + }); 516 + }); 517 + }, 518 + function () { 519 + return new Promise(function ($resolve) { 520 + Loop::addTimer(1, function () use ($resolve) { 521 + $resolve('Slept for another whole second'); 522 + }); 523 + }); 524 + }, 525 + function () { 526 + return new Promise(function ($resolve) { 527 + Loop::addTimer(1, function () use ($resolve) { 528 + $resolve('Slept for yet another whole second'); 529 + }); 530 + }); 531 + }, 532 + ])->then(function (array $results) { 533 + foreach ($results as $result) { 534 + var_dump($result); 535 + } 536 + }, function (Exception $e) { 537 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 538 + }); 539 + ``` 540 + 541 + ### series() 542 + 543 + The `series(iterable<callable():PromiseInterface<T>> $tasks): PromiseInterface<array<T>>` function can be used 544 + like this: 545 + 546 + ```php 547 + <?php 548 + 549 + use React\EventLoop\Loop; 550 + use React\Promise\Promise; 551 + 552 + React\Async\series([ 553 + function () { 554 + return new Promise(function ($resolve) { 555 + Loop::addTimer(1, function () use ($resolve) { 556 + $resolve('Slept for a whole second'); 557 + }); 558 + }); 559 + }, 560 + function () { 561 + return new Promise(function ($resolve) { 562 + Loop::addTimer(1, function () use ($resolve) { 563 + $resolve('Slept for another whole second'); 564 + }); 565 + }); 566 + }, 567 + function () { 568 + return new Promise(function ($resolve) { 569 + Loop::addTimer(1, function () use ($resolve) { 570 + $resolve('Slept for yet another whole second'); 571 + }); 572 + }); 573 + }, 574 + ])->then(function (array $results) { 575 + foreach ($results as $result) { 576 + var_dump($result); 577 + } 578 + }, function (Exception $e) { 579 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 580 + }); 581 + ``` 582 + 583 + ### waterfall() 584 + 585 + The `waterfall(iterable<callable(mixed=):PromiseInterface<T>> $tasks): PromiseInterface<T>` function can be used 586 + like this: 587 + 588 + ```php 589 + <?php 590 + 591 + use React\EventLoop\Loop; 592 + use React\Promise\Promise; 593 + 594 + $addOne = function ($prev = 0) { 595 + return new Promise(function ($resolve) use ($prev) { 596 + Loop::addTimer(1, function () use ($prev, $resolve) { 597 + $resolve($prev + 1); 598 + }); 599 + }); 600 + }; 601 + 602 + React\Async\waterfall([ 603 + $addOne, 604 + $addOne, 605 + $addOne 606 + ])->then(function ($prev) { 607 + echo "Final result is $prev\n"; 608 + }, function (Exception $e) { 609 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 610 + }); 611 + ``` 612 + 613 + ## Todo 614 + 615 + * Implement queue() 616 + 617 + ## Install 618 + 619 + The recommended way to install this library is [through Composer](https://getcomposer.org/). 620 + [New to Composer?](https://getcomposer.org/doc/00-intro.md) 621 + 622 + This project follows [SemVer](https://semver.org/). 623 + This will install the latest supported version from this branch: 624 + 625 + ```bash 626 + composer require react/async:^4.3 627 + ``` 628 + 629 + See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. 630 + 631 + This project aims to run on any platform and thus does not require any PHP 632 + extensions and supports running on PHP 8.1+. 633 + It's *highly recommended to use the latest supported PHP version* for this project. 634 + 635 + We're committed to providing long-term support (LTS) options and to provide a 636 + smooth upgrade path. If you're using an older PHP version, you may use the 637 + [`3.x` branch](https://github.com/reactphp/async/tree/3.x) (PHP 7.1+) or 638 + [`2.x` branch](https://github.com/reactphp/async/tree/2.x) (PHP 5.3+) which both 639 + provide a compatible API but do not take advantage of newer language features. 640 + You may target multiple versions at the same time to support a wider range of 641 + PHP versions like this: 642 + 643 + ```bash 644 + composer require "react/async:^4 || ^3 || ^2" 645 + ``` 646 + 647 + ## Tests 648 + 649 + To run the test suite, you first need to clone this repo and then install all 650 + dependencies [through Composer](https://getcomposer.org/): 651 + 652 + ```bash 653 + composer install 654 + ``` 655 + 656 + To run the test suite, go to the project root and run: 657 + 658 + ```bash 659 + vendor/bin/phpunit 660 + ``` 661 + 662 + On top of this, we use PHPStan on max level to ensure type safety across the project: 663 + 664 + ```bash 665 + vendor/bin/phpstan 666 + ``` 667 + 668 + ## License 669 + 670 + MIT, see [LICENSE file](LICENSE). 671 + 672 + This project is heavily influenced by [async.js](https://github.com/caolan/async).
+50
vendor/react/async/composer.json
··· 1 + { 2 + "name": "react/async", 3 + "description": "Async utilities and fibers for ReactPHP", 4 + "keywords": ["async", "ReactPHP"], 5 + "license": "MIT", 6 + "authors": [ 7 + { 8 + "name": "Christian Lück", 9 + "homepage": "https://clue.engineering/", 10 + "email": "christian@clue.engineering" 11 + }, 12 + { 13 + "name": "Cees-Jan Kiewiet", 14 + "homepage": "https://wyrihaximus.net/", 15 + "email": "reactphp@ceesjankiewiet.nl" 16 + }, 17 + { 18 + "name": "Jan Sorgalla", 19 + "homepage": "https://sorgalla.com/", 20 + "email": "jsorgalla@gmail.com" 21 + }, 22 + { 23 + "name": "Chris Boden", 24 + "homepage": "https://cboden.dev/", 25 + "email": "cboden@gmail.com" 26 + } 27 + ], 28 + "require": { 29 + "php": ">=8.1", 30 + "react/event-loop": "^1.2", 31 + "react/promise": "^3.2 || ^2.8 || ^1.2.1" 32 + }, 33 + "require-dev": { 34 + "phpstan/phpstan": "1.10.39", 35 + "phpunit/phpunit": "^9.6" 36 + }, 37 + "autoload": { 38 + "psr-4": { 39 + "React\\Async\\": "src/" 40 + }, 41 + "files": [ 42 + "src/functions_include.php" 43 + ] 44 + }, 45 + "autoload-dev": { 46 + "psr-4": { 47 + "React\\Tests\\Async\\": "tests/" 48 + } 49 + } 50 + }
+33
vendor/react/async/src/FiberFactory.php
··· 1 + <?php 2 + 3 + namespace React\Async; 4 + 5 + /** 6 + * This factory its only purpose is interoperability. Where with 7 + * event loops one could simply wrap another event loop. But with fibers 8 + * that has become impossible and as such we provide this factory and the 9 + * FiberInterface. 10 + * 11 + * Usage is not documented and as such not supported and might chang without 12 + * notice. Use at your own risk. 13 + * 14 + * @internal 15 + */ 16 + final class FiberFactory 17 + { 18 + private static ?\Closure $factory = null; 19 + 20 + public static function create(): FiberInterface 21 + { 22 + return (self::factory())(); 23 + } 24 + 25 + public static function factory(?\Closure $factory = null): \Closure 26 + { 27 + if ($factory !== null) { 28 + self::$factory = $factory; 29 + } 30 + 31 + return self::$factory ?? static fn (): FiberInterface => new SimpleFiber(); 32 + } 33 + }
+23
vendor/react/async/src/FiberInterface.php
··· 1 + <?php 2 + 3 + namespace React\Async; 4 + 5 + /** 6 + * This interface its only purpose is interoperability. Where with 7 + * event loops one could simply wrap another event loop. But with fibers 8 + * that has become impossible and as such we provide this interface and the 9 + * FiberFactory. 10 + * 11 + * Usage is not documented and as such not supported and might chang without 12 + * notice. Use at your own risk. 13 + * 14 + * @internal 15 + */ 16 + interface FiberInterface 17 + { 18 + public function resume(mixed $value): void; 19 + 20 + public function throw(\Throwable $throwable): void; 21 + 22 + public function suspend(): mixed; 23 + }
+42
vendor/react/async/src/FiberMap.php
··· 1 + <?php 2 + 3 + namespace React\Async; 4 + 5 + use React\Promise\PromiseInterface; 6 + 7 + /** 8 + * @internal 9 + * 10 + * @template T 11 + */ 12 + final class FiberMap 13 + { 14 + /** @var array<int,PromiseInterface<T>> */ 15 + private static array $map = []; 16 + 17 + /** 18 + * @param \Fiber<mixed,mixed,mixed,mixed> $fiber 19 + * @param PromiseInterface<T> $promise 20 + */ 21 + public static function setPromise(\Fiber $fiber, PromiseInterface $promise): void 22 + { 23 + self::$map[\spl_object_id($fiber)] = $promise; 24 + } 25 + 26 + /** 27 + * @param \Fiber<mixed,mixed,mixed,mixed> $fiber 28 + */ 29 + public static function unsetPromise(\Fiber $fiber): void 30 + { 31 + unset(self::$map[\spl_object_id($fiber)]); 32 + } 33 + 34 + /** 35 + * @param \Fiber<mixed,mixed,mixed,mixed> $fiber 36 + * @return ?PromiseInterface<T> 37 + */ 38 + public static function getPromise(\Fiber $fiber): ?PromiseInterface 39 + { 40 + return self::$map[\spl_object_id($fiber)] ?? null; 41 + } 42 + }
+79
vendor/react/async/src/SimpleFiber.php
··· 1 + <?php 2 + 3 + namespace React\Async; 4 + 5 + use React\EventLoop\Loop; 6 + 7 + /** 8 + * @internal 9 + */ 10 + final class SimpleFiber implements FiberInterface 11 + { 12 + /** @var ?\Fiber<void,void,void,callable(): mixed> */ 13 + private static ?\Fiber $scheduler = null; 14 + 15 + private static ?\Closure $suspend = null; 16 + 17 + /** @var ?\Fiber<mixed,mixed,mixed,mixed> */ 18 + private ?\Fiber $fiber = null; 19 + 20 + public function __construct() 21 + { 22 + $this->fiber = \Fiber::getCurrent(); 23 + } 24 + 25 + public function resume(mixed $value): void 26 + { 27 + if ($this->fiber !== null) { 28 + $this->fiber->resume($value); 29 + } else { 30 + self::$suspend = static fn() => $value; 31 + } 32 + 33 + if (self::$suspend !== null && \Fiber::getCurrent() === self::$scheduler) { 34 + $suspend = self::$suspend; 35 + self::$suspend = null; 36 + 37 + \Fiber::suspend($suspend); 38 + } 39 + } 40 + 41 + public function throw(\Throwable $throwable): void 42 + { 43 + if ($this->fiber !== null) { 44 + $this->fiber->throw($throwable); 45 + } else { 46 + self::$suspend = static fn() => throw $throwable; 47 + } 48 + 49 + if (self::$suspend !== null && \Fiber::getCurrent() === self::$scheduler) { 50 + $suspend = self::$suspend; 51 + self::$suspend = null; 52 + 53 + \Fiber::suspend($suspend); 54 + } 55 + } 56 + 57 + public function suspend(): mixed 58 + { 59 + if ($this->fiber === null) { 60 + if (self::$scheduler === null || self::$scheduler->isTerminated()) { 61 + self::$scheduler = new \Fiber(static fn() => Loop::run()); 62 + // Run event loop to completion on shutdown. 63 + \register_shutdown_function(static function (): void { 64 + assert(self::$scheduler instanceof \Fiber); 65 + if (self::$scheduler->isSuspended()) { 66 + self::$scheduler->resume(); 67 + } 68 + }); 69 + } 70 + 71 + $ret = (self::$scheduler->isStarted() ? self::$scheduler->resume() : self::$scheduler->start()); 72 + assert(\is_callable($ret)); 73 + 74 + return $ret(); 75 + } 76 + 77 + return \Fiber::suspend(); 78 + } 79 + }
+846
vendor/react/async/src/functions.php
··· 1 + <?php 2 + 3 + namespace React\Async; 4 + 5 + use React\EventLoop\Loop; 6 + use React\EventLoop\TimerInterface; 7 + use React\Promise\Deferred; 8 + use React\Promise\Promise; 9 + use React\Promise\PromiseInterface; 10 + use function React\Promise\reject; 11 + use function React\Promise\resolve; 12 + 13 + /** 14 + * Return an async function for a function that uses [`await()`](#await) internally. 15 + * 16 + * This function is specifically designed to complement the [`await()` function](#await). 17 + * The [`await()` function](#await) can be considered *blocking* from the 18 + * perspective of the calling code. You can avoid this blocking behavior by 19 + * wrapping it in an `async()` function call. Everything inside this function 20 + * will still be blocked, but everything outside this function can be executed 21 + * asynchronously without blocking: 22 + * 23 + * ```php 24 + * Loop::addTimer(0.5, React\Async\async(function () { 25 + * echo 'a'; 26 + * React\Async\await(React\Promise\Timer\sleep(1.0)); 27 + * echo 'c'; 28 + * })); 29 + * 30 + * Loop::addTimer(1.0, function () { 31 + * echo 'b'; 32 + * }); 33 + * 34 + * // prints "a" at t=0.5s 35 + * // prints "b" at t=1.0s 36 + * // prints "c" at t=1.5s 37 + * ``` 38 + * 39 + * See also the [`await()` function](#await) for more details. 40 + * 41 + * Note that this function only works in tandem with the [`await()` function](#await). 42 + * In particular, this function does not "magically" make any blocking function 43 + * non-blocking: 44 + * 45 + * ```php 46 + * Loop::addTimer(0.5, React\Async\async(function () { 47 + * echo 'a'; 48 + * sleep(1); // broken: using PHP's blocking sleep() for demonstration purposes 49 + * echo 'c'; 50 + * })); 51 + * 52 + * Loop::addTimer(1.0, function () { 53 + * echo 'b'; 54 + * }); 55 + * 56 + * // prints "a" at t=0.5s 57 + * // prints "c" at t=1.5s: Correct timing, but wrong order 58 + * // prints "b" at t=1.5s: Triggered too late because it was blocked 59 + * ``` 60 + * 61 + * As an alternative, you should always make sure to use this function in tandem 62 + * with the [`await()` function](#await) and an async API returning a promise 63 + * as shown in the previous example. 64 + * 65 + * The `async()` function is specifically designed for cases where it is used 66 + * as a callback (such as an event loop timer, event listener, or promise 67 + * callback). For this reason, it returns a new function wrapping the given 68 + * `$function` instead of directly invoking it and returning its value. 69 + * 70 + * ```php 71 + * use function React\Async\async; 72 + * 73 + * Loop::addTimer(1.0, async(function () { … })); 74 + * $connection->on('close', async(function () { … })); 75 + * $stream->on('data', async(function ($data) { … })); 76 + * $promise->then(async(function (int $result) { … })); 77 + * ``` 78 + * 79 + * You can invoke this wrapping function to invoke the given `$function` with 80 + * any arguments given as-is. The function will always return a Promise which 81 + * will be fulfilled with whatever your `$function` returns. Likewise, it will 82 + * return a promise that will be rejected if you throw an `Exception` or 83 + * `Throwable` from your `$function`. This allows you to easily create 84 + * Promise-based functions: 85 + * 86 + * ```php 87 + * $promise = React\Async\async(function (): int { 88 + * $browser = new React\Http\Browser(); 89 + * $urls = [ 90 + * 'https://example.com/alice', 91 + * 'https://example.com/bob' 92 + * ]; 93 + * 94 + * $bytes = 0; 95 + * foreach ($urls as $url) { 96 + * $response = React\Async\await($browser->get($url)); 97 + * assert($response instanceof Psr\Http\Message\ResponseInterface); 98 + * $bytes += $response->getBody()->getSize(); 99 + * } 100 + * return $bytes; 101 + * })(); 102 + * 103 + * $promise->then(function (int $bytes) { 104 + * echo 'Total size: ' . $bytes . PHP_EOL; 105 + * }, function (Exception $e) { 106 + * echo 'Error: ' . $e->getMessage() . PHP_EOL; 107 + * }); 108 + * ``` 109 + * 110 + * The previous example uses [`await()`](#await) inside a loop to highlight how 111 + * this vastly simplifies consuming asynchronous operations. At the same time, 112 + * this naive example does not leverage concurrent execution, as it will 113 + * essentially "await" between each operation. In order to take advantage of 114 + * concurrent execution within the given `$function`, you can "await" multiple 115 + * promises by using a single [`await()`](#await) together with Promise-based 116 + * primitives like this: 117 + * 118 + * ```php 119 + * $promise = React\Async\async(function (): int { 120 + * $browser = new React\Http\Browser(); 121 + * $urls = [ 122 + * 'https://example.com/alice', 123 + * 'https://example.com/bob' 124 + * ]; 125 + * 126 + * $promises = []; 127 + * foreach ($urls as $url) { 128 + * $promises[] = $browser->get($url); 129 + * } 130 + * 131 + * try { 132 + * $responses = React\Async\await(React\Promise\all($promises)); 133 + * } catch (Exception $e) { 134 + * foreach ($promises as $promise) { 135 + * $promise->cancel(); 136 + * } 137 + * throw $e; 138 + * } 139 + * 140 + * $bytes = 0; 141 + * foreach ($responses as $response) { 142 + * assert($response instanceof Psr\Http\Message\ResponseInterface); 143 + * $bytes += $response->getBody()->getSize(); 144 + * } 145 + * return $bytes; 146 + * })(); 147 + * 148 + * $promise->then(function (int $bytes) { 149 + * echo 'Total size: ' . $bytes . PHP_EOL; 150 + * }, function (Exception $e) { 151 + * echo 'Error: ' . $e->getMessage() . PHP_EOL; 152 + * }); 153 + * ``` 154 + * 155 + * The returned promise is implemented in such a way that it can be cancelled 156 + * when it is still pending. Cancelling a pending promise will cancel any awaited 157 + * promises inside that fiber or any nested fibers. As such, the following example 158 + * will only output `ab` and cancel the pending [`delay()`](#delay). 159 + * The [`await()`](#await) calls in this example would throw a `RuntimeException` 160 + * from the cancelled [`delay()`](#delay) call that bubbles up through the fibers. 161 + * 162 + * ```php 163 + * $promise = async(static function (): int { 164 + * echo 'a'; 165 + * await(async(static function (): void { 166 + * echo 'b'; 167 + * delay(2); 168 + * echo 'c'; 169 + * })()); 170 + * echo 'd'; 171 + * 172 + * return time(); 173 + * })(); 174 + * 175 + * $promise->cancel(); 176 + * await($promise); 177 + * ``` 178 + * 179 + * @template T 180 + * @template A1 (any number of function arguments, see https://github.com/phpstan/phpstan/issues/8214) 181 + * @template A2 182 + * @template A3 183 + * @template A4 184 + * @template A5 185 + * @param callable(A1,A2,A3,A4,A5): (PromiseInterface<T>|T) $function 186 + * @return callable(A1=,A2=,A3=,A4=,A5=): PromiseInterface<T> 187 + * @since 4.0.0 188 + * @see coroutine() 189 + */ 190 + function async(callable $function): callable 191 + { 192 + return static function (mixed ...$args) use ($function): PromiseInterface { 193 + $fiber = null; 194 + /** @var PromiseInterface<T> $promise*/ 195 + $promise = new Promise(function (callable $resolve, callable $reject) use ($function, $args, &$fiber): void { 196 + $fiber = new \Fiber(function () use ($resolve, $reject, $function, $args, &$fiber): void { 197 + try { 198 + $resolve($function(...$args)); 199 + } catch (\Throwable $exception) { 200 + $reject($exception); 201 + } finally { 202 + assert($fiber instanceof \Fiber); 203 + FiberMap::unsetPromise($fiber); 204 + } 205 + }); 206 + 207 + $fiber->start(); 208 + }, function () use (&$fiber): void { 209 + assert($fiber instanceof \Fiber); 210 + $promise = FiberMap::getPromise($fiber); 211 + if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) { 212 + $promise->cancel(); 213 + } 214 + }); 215 + 216 + $lowLevelFiber = \Fiber::getCurrent(); 217 + if ($lowLevelFiber !== null) { 218 + FiberMap::setPromise($lowLevelFiber, $promise); 219 + } 220 + 221 + return $promise; 222 + }; 223 + } 224 + 225 + /** 226 + * Block waiting for the given `$promise` to be fulfilled. 227 + * 228 + * ```php 229 + * $result = React\Async\await($promise); 230 + * ``` 231 + * 232 + * This function will only return after the given `$promise` has settled, i.e. 233 + * either fulfilled or rejected. While the promise is pending, this function 234 + * can be considered *blocking* from the perspective of the calling code. 235 + * You can avoid this blocking behavior by wrapping it in an [`async()` function](#async) 236 + * call. Everything inside this function will still be blocked, but everything 237 + * outside this function can be executed asynchronously without blocking: 238 + * 239 + * ```php 240 + * Loop::addTimer(0.5, React\Async\async(function () { 241 + * echo 'a'; 242 + * React\Async\await(React\Promise\Timer\sleep(1.0)); 243 + * echo 'c'; 244 + * })); 245 + * 246 + * Loop::addTimer(1.0, function () { 247 + * echo 'b'; 248 + * }); 249 + * 250 + * // prints "a" at t=0.5s 251 + * // prints "b" at t=1.0s 252 + * // prints "c" at t=1.5s 253 + * ``` 254 + * 255 + * See also the [`async()` function](#async) for more details. 256 + * 257 + * Once the promise is fulfilled, this function will return whatever the promise 258 + * resolved to. 259 + * 260 + * Once the promise is rejected, this will throw whatever the promise rejected 261 + * with. If the promise did not reject with an `Exception` or `Throwable`, then 262 + * this function will throw an `UnexpectedValueException` instead. 263 + * 264 + * ```php 265 + * try { 266 + * $result = React\Async\await($promise); 267 + * // promise successfully fulfilled with $result 268 + * echo 'Result: ' . $result; 269 + * } catch (Throwable $e) { 270 + * // promise rejected with $e 271 + * echo 'Error: ' . $e->getMessage(); 272 + * } 273 + * ``` 274 + * 275 + * @template T 276 + * @param PromiseInterface<T> $promise 277 + * @return T returns whatever the promise resolves to 278 + * @throws \Exception when the promise is rejected with an `Exception` 279 + * @throws \Throwable when the promise is rejected with a `Throwable` 280 + * @throws \UnexpectedValueException when the promise is rejected with an unexpected value (Promise API v1 or v2 only) 281 + */ 282 + function await(PromiseInterface $promise): mixed 283 + { 284 + $fiber = null; 285 + $resolved = false; 286 + $rejected = false; 287 + 288 + /** @var T $resolvedValue */ 289 + $resolvedValue = null; 290 + $rejectedThrowable = null; 291 + $lowLevelFiber = \Fiber::getCurrent(); 292 + 293 + $promise->then( 294 + function (mixed $value) use (&$resolved, &$resolvedValue, &$fiber, $lowLevelFiber): void { 295 + if ($lowLevelFiber !== null) { 296 + FiberMap::unsetPromise($lowLevelFiber); 297 + } 298 + 299 + /** @var ?\Fiber<mixed,mixed,mixed,mixed> $fiber */ 300 + if ($fiber === null) { 301 + $resolved = true; 302 + /** @var T $resolvedValue */ 303 + $resolvedValue = $value; 304 + return; 305 + } 306 + 307 + $fiber->resume($value); 308 + }, 309 + function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber, $lowLevelFiber): void { 310 + if ($lowLevelFiber !== null) { 311 + FiberMap::unsetPromise($lowLevelFiber); 312 + } 313 + 314 + if (!$throwable instanceof \Throwable) { 315 + $throwable = new \UnexpectedValueException( 316 + 'Promise rejected with unexpected value of type ' . (is_object($throwable) ? get_class($throwable) : gettype($throwable)) /** @phpstan-ignore-line */ 317 + ); 318 + 319 + // avoid garbage references by replacing all closures in call stack. 320 + // what a lovely piece of code! 321 + $r = new \ReflectionProperty('Exception', 'trace'); 322 + $trace = $r->getValue($throwable); 323 + assert(\is_array($trace)); 324 + 325 + // Exception trace arguments only available when zend.exception_ignore_args is not set 326 + // @codeCoverageIgnoreStart 327 + foreach ($trace as $ti => $one) { 328 + if (isset($one['args'])) { 329 + foreach ($one['args'] as $ai => $arg) { 330 + if ($arg instanceof \Closure) { 331 + $trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')'; 332 + } 333 + } 334 + } 335 + } 336 + // @codeCoverageIgnoreEnd 337 + $r->setValue($throwable, $trace); 338 + } 339 + 340 + if ($fiber === null) { 341 + $rejected = true; 342 + $rejectedThrowable = $throwable; 343 + return; 344 + } 345 + 346 + $fiber->throw($throwable); 347 + } 348 + ); 349 + 350 + if ($resolved) { 351 + return $resolvedValue; 352 + } 353 + 354 + if ($rejected) { 355 + assert($rejectedThrowable instanceof \Throwable); 356 + throw $rejectedThrowable; 357 + } 358 + 359 + if ($lowLevelFiber !== null) { 360 + FiberMap::setPromise($lowLevelFiber, $promise); 361 + } 362 + 363 + $fiber = FiberFactory::create(); 364 + 365 + return $fiber->suspend(); 366 + } 367 + 368 + /** 369 + * Delay program execution for duration given in `$seconds`. 370 + * 371 + * ```php 372 + * React\Async\delay($seconds); 373 + * ``` 374 + * 375 + * This function will only return after the given number of `$seconds` have 376 + * elapsed. If there are no other events attached to this loop, it will behave 377 + * similar to PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php). 378 + * 379 + * ```php 380 + * echo 'a'; 381 + * React\Async\delay(1.0); 382 + * echo 'b'; 383 + * 384 + * // prints "a" at t=0.0s 385 + * // prints "b" at t=1.0s 386 + * ``` 387 + * 388 + * Unlike PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php), 389 + * this function may not necessarily halt execution of the entire process thread. 390 + * Instead, it allows the event loop to run any other events attached to the 391 + * same loop until the delay returns: 392 + * 393 + * ```php 394 + * echo 'a'; 395 + * Loop::addTimer(1.0, function (): void { 396 + * echo 'b'; 397 + * }); 398 + * React\Async\delay(3.0); 399 + * echo 'c'; 400 + * 401 + * // prints "a" at t=0.0s 402 + * // prints "b" at t=1.0s 403 + * // prints "c" at t=3.0s 404 + * ``` 405 + * 406 + * This behavior is especially useful if you want to delay the program execution 407 + * of a particular routine, such as when building a simple polling or retry 408 + * mechanism: 409 + * 410 + * ```php 411 + * try { 412 + * something(); 413 + * } catch (Throwable) { 414 + * // in case of error, retry after a short delay 415 + * React\Async\delay(1.0); 416 + * something(); 417 + * } 418 + * ``` 419 + * 420 + * Because this function only returns after some time has passed, it can be 421 + * considered *blocking* from the perspective of the calling code. You can avoid 422 + * this blocking behavior by wrapping it in an [`async()` function](#async) call. 423 + * Everything inside this function will still be blocked, but everything outside 424 + * this function can be executed asynchronously without blocking: 425 + * 426 + * ```php 427 + * Loop::addTimer(0.5, React\Async\async(function (): void { 428 + * echo 'a'; 429 + * React\Async\delay(1.0); 430 + * echo 'c'; 431 + * })); 432 + * 433 + * Loop::addTimer(1.0, function (): void { 434 + * echo 'b'; 435 + * }); 436 + * 437 + * // prints "a" at t=0.5s 438 + * // prints "b" at t=1.0s 439 + * // prints "c" at t=1.5s 440 + * ``` 441 + * 442 + * See also the [`async()` function](#async) for more details. 443 + * 444 + * Internally, the `$seconds` argument will be used as a timer for the loop so that 445 + * it keeps running until this timer triggers. This implies that if you pass a 446 + * really small (or negative) value, it will still start a timer and will thus 447 + * trigger at the earliest possible time in the future. 448 + * 449 + * The function is implemented in such a way that it can be cancelled when it is 450 + * running inside an [`async()` function](#async). Cancelling the resulting 451 + * promise will clean up any pending timers and throw a `RuntimeException` from 452 + * the pending delay which in turn would reject the resulting promise. 453 + * 454 + * ```php 455 + * $promise = async(function (): void { 456 + * echo 'a'; 457 + * delay(3.0); 458 + * echo 'b'; 459 + * })(); 460 + * 461 + * Loop::addTimer(2.0, function () use ($promise): void { 462 + * $promise->cancel(); 463 + * }); 464 + * 465 + * // prints "a" at t=0.0s 466 + * // rejects $promise at t=2.0 467 + * // never prints "b" 468 + * ``` 469 + * 470 + * @return void 471 + * @throws \RuntimeException when the function is cancelled inside an `async()` function 472 + * @see async() 473 + * @uses await() 474 + */ 475 + function delay(float $seconds): void 476 + { 477 + /** @var ?TimerInterface $timer */ 478 + $timer = null; 479 + 480 + await(new Promise(function (callable $resolve) use ($seconds, &$timer): void { 481 + $timer = Loop::addTimer($seconds, fn() => $resolve(null)); 482 + }, function () use (&$timer): void { 483 + assert($timer instanceof TimerInterface); 484 + Loop::cancelTimer($timer); 485 + throw new \RuntimeException('Delay cancelled'); 486 + })); 487 + } 488 + 489 + /** 490 + * Execute a Generator-based coroutine to "await" promises. 491 + * 492 + * ```php 493 + * React\Async\coroutine(function () { 494 + * $browser = new React\Http\Browser(); 495 + * 496 + * try { 497 + * $response = yield $browser->get('https://example.com/'); 498 + * assert($response instanceof Psr\Http\Message\ResponseInterface); 499 + * echo $response->getBody(); 500 + * } catch (Exception $e) { 501 + * echo 'Error: ' . $e->getMessage() . PHP_EOL; 502 + * } 503 + * }); 504 + * ``` 505 + * 506 + * Using Generator-based coroutines is an alternative to directly using the 507 + * underlying promise APIs. For many use cases, this makes using promise-based 508 + * APIs much simpler, as it resembles a synchronous code flow more closely. 509 + * The above example performs the equivalent of directly using the promise APIs: 510 + * 511 + * ```php 512 + * $browser = new React\Http\Browser(); 513 + * 514 + * $browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) { 515 + * echo $response->getBody(); 516 + * }, function (Exception $e) { 517 + * echo 'Error: ' . $e->getMessage() . PHP_EOL; 518 + * }); 519 + * ``` 520 + * 521 + * The `yield` keyword can be used to "await" a promise resolution. Internally, 522 + * it will turn the entire given `$function` into a [`Generator`](https://www.php.net/manual/en/class.generator.php). 523 + * This allows the execution to be interrupted and resumed at the same place 524 + * when the promise is fulfilled. The `yield` statement returns whatever the 525 + * promise is fulfilled with. If the promise is rejected, it will throw an 526 + * `Exception` or `Throwable`. 527 + * 528 + * The `coroutine()` function will always return a Promise which will be 529 + * fulfilled with whatever your `$function` returns. Likewise, it will return 530 + * a promise that will be rejected if you throw an `Exception` or `Throwable` 531 + * from your `$function`. This allows you to easily create Promise-based 532 + * functions: 533 + * 534 + * ```php 535 + * $promise = React\Async\coroutine(function () { 536 + * $browser = new React\Http\Browser(); 537 + * $urls = [ 538 + * 'https://example.com/alice', 539 + * 'https://example.com/bob' 540 + * ]; 541 + * 542 + * $bytes = 0; 543 + * foreach ($urls as $url) { 544 + * $response = yield $browser->get($url); 545 + * assert($response instanceof Psr\Http\Message\ResponseInterface); 546 + * $bytes += $response->getBody()->getSize(); 547 + * } 548 + * return $bytes; 549 + * }); 550 + * 551 + * $promise->then(function (int $bytes) { 552 + * echo 'Total size: ' . $bytes . PHP_EOL; 553 + * }, function (Exception $e) { 554 + * echo 'Error: ' . $e->getMessage() . PHP_EOL; 555 + * }); 556 + * ``` 557 + * 558 + * The previous example uses a `yield` statement inside a loop to highlight how 559 + * this vastly simplifies consuming asynchronous operations. At the same time, 560 + * this naive example does not leverage concurrent execution, as it will 561 + * essentially "await" between each operation. In order to take advantage of 562 + * concurrent execution within the given `$function`, you can "await" multiple 563 + * promises by using a single `yield` together with Promise-based primitives 564 + * like this: 565 + * 566 + * ```php 567 + * $promise = React\Async\coroutine(function () { 568 + * $browser = new React\Http\Browser(); 569 + * $urls = [ 570 + * 'https://example.com/alice', 571 + * 'https://example.com/bob' 572 + * ]; 573 + * 574 + * $promises = []; 575 + * foreach ($urls as $url) { 576 + * $promises[] = $browser->get($url); 577 + * } 578 + * 579 + * try { 580 + * $responses = yield React\Promise\all($promises); 581 + * } catch (Exception $e) { 582 + * foreach ($promises as $promise) { 583 + * $promise->cancel(); 584 + * } 585 + * throw $e; 586 + * } 587 + * 588 + * $bytes = 0; 589 + * foreach ($responses as $response) { 590 + * assert($response instanceof Psr\Http\Message\ResponseInterface); 591 + * $bytes += $response->getBody()->getSize(); 592 + * } 593 + * return $bytes; 594 + * }); 595 + * 596 + * $promise->then(function (int $bytes) { 597 + * echo 'Total size: ' . $bytes . PHP_EOL; 598 + * }, function (Exception $e) { 599 + * echo 'Error: ' . $e->getMessage() . PHP_EOL; 600 + * }); 601 + * ``` 602 + * 603 + * @template T 604 + * @template TYield 605 + * @template A1 (any number of function arguments, see https://github.com/phpstan/phpstan/issues/8214) 606 + * @template A2 607 + * @template A3 608 + * @template A4 609 + * @template A5 610 + * @param callable(A1, A2, A3, A4, A5):(\Generator<mixed, PromiseInterface<TYield>, TYield, PromiseInterface<T>|T>|PromiseInterface<T>|T) $function 611 + * @param mixed ...$args Optional list of additional arguments that will be passed to the given `$function` as is 612 + * @return PromiseInterface<T> 613 + * @since 3.0.0 614 + */ 615 + function coroutine(callable $function, mixed ...$args): PromiseInterface 616 + { 617 + try { 618 + $generator = $function(...$args); 619 + } catch (\Throwable $e) { 620 + return reject($e); 621 + } 622 + 623 + if (!$generator instanceof \Generator) { 624 + return resolve($generator); 625 + } 626 + 627 + $promise = null; 628 + /** @var Deferred<T> $deferred*/ 629 + $deferred = new Deferred(function () use (&$promise) { 630 + /** @var ?PromiseInterface<T> $promise */ 631 + if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) { 632 + $promise->cancel(); 633 + } 634 + $promise = null; 635 + }); 636 + 637 + /** @var callable $next */ 638 + $next = function () use ($deferred, $generator, &$next, &$promise) { 639 + try { 640 + if (!$generator->valid()) { 641 + $next = null; 642 + $deferred->resolve($generator->getReturn()); 643 + return; 644 + } 645 + } catch (\Throwable $e) { 646 + $next = null; 647 + $deferred->reject($e); 648 + return; 649 + } 650 + 651 + $promise = $generator->current(); 652 + if (!$promise instanceof PromiseInterface) { 653 + $next = null; 654 + $deferred->reject(new \UnexpectedValueException( 655 + 'Expected coroutine to yield ' . PromiseInterface::class . ', but got ' . (is_object($promise) ? get_class($promise) : gettype($promise)) 656 + )); 657 + return; 658 + } 659 + 660 + /** @var PromiseInterface<TYield> $promise */ 661 + assert($next instanceof \Closure); 662 + $promise->then(function ($value) use ($generator, $next) { 663 + $generator->send($value); 664 + $next(); 665 + }, function (\Throwable $reason) use ($generator, $next) { 666 + $generator->throw($reason); 667 + $next(); 668 + })->then(null, function (\Throwable $reason) use ($deferred, &$next) { 669 + $next = null; 670 + $deferred->reject($reason); 671 + }); 672 + }; 673 + $next(); 674 + 675 + return $deferred->promise(); 676 + } 677 + 678 + /** 679 + * @template T 680 + * @param iterable<callable():(PromiseInterface<T>|T)> $tasks 681 + * @return PromiseInterface<array<T>> 682 + */ 683 + function parallel(iterable $tasks): PromiseInterface 684 + { 685 + /** @var array<int,PromiseInterface<T>> $pending */ 686 + $pending = []; 687 + /** @var Deferred<array<T>> $deferred */ 688 + $deferred = new Deferred(function () use (&$pending) { 689 + foreach ($pending as $promise) { 690 + if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) { 691 + $promise->cancel(); 692 + } 693 + } 694 + $pending = []; 695 + }); 696 + $results = []; 697 + $continue = true; 698 + 699 + $taskErrback = function ($error) use (&$pending, $deferred, &$continue) { 700 + $continue = false; 701 + $deferred->reject($error); 702 + 703 + foreach ($pending as $promise) { 704 + if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) { 705 + $promise->cancel(); 706 + } 707 + } 708 + $pending = []; 709 + }; 710 + 711 + foreach ($tasks as $i => $task) { 712 + $taskCallback = function ($result) use (&$results, &$pending, &$continue, $i, $deferred) { 713 + $results[$i] = $result; 714 + unset($pending[$i]); 715 + 716 + if (!$pending && !$continue) { 717 + $deferred->resolve($results); 718 + } 719 + }; 720 + 721 + $promise = \call_user_func($task); 722 + assert($promise instanceof PromiseInterface); 723 + $pending[$i] = $promise; 724 + 725 + $promise->then($taskCallback, $taskErrback); 726 + 727 + if (!$continue) { 728 + break; 729 + } 730 + } 731 + 732 + $continue = false; 733 + if (!$pending) { 734 + $deferred->resolve($results); 735 + } 736 + 737 + /** @var PromiseInterface<array<T>> Remove once defining `Deferred()` above is supported by PHPStan, see https://github.com/phpstan/phpstan/issues/11032 */ 738 + return $deferred->promise(); 739 + } 740 + 741 + /** 742 + * @template T 743 + * @param iterable<callable():(PromiseInterface<T>|T)> $tasks 744 + * @return PromiseInterface<array<T>> 745 + */ 746 + function series(iterable $tasks): PromiseInterface 747 + { 748 + $pending = null; 749 + /** @var Deferred<array<T>> $deferred */ 750 + $deferred = new Deferred(function () use (&$pending) { 751 + /** @var ?PromiseInterface<T> $pending */ 752 + if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) { 753 + $pending->cancel(); 754 + } 755 + $pending = null; 756 + }); 757 + $results = []; 758 + 759 + if ($tasks instanceof \IteratorAggregate) { 760 + $tasks = $tasks->getIterator(); 761 + assert($tasks instanceof \Iterator); 762 + } 763 + 764 + $taskCallback = function ($result) use (&$results, &$next) { 765 + $results[] = $result; 766 + /** @var \Closure $next */ 767 + $next(); 768 + }; 769 + 770 + $next = function () use (&$tasks, $taskCallback, $deferred, &$results, &$pending) { 771 + if ($tasks instanceof \Iterator ? !$tasks->valid() : !$tasks) { 772 + $deferred->resolve($results); 773 + return; 774 + } 775 + 776 + if ($tasks instanceof \Iterator) { 777 + $task = $tasks->current(); 778 + $tasks->next(); 779 + } else { 780 + assert(\is_array($tasks)); 781 + $task = \array_shift($tasks); 782 + } 783 + 784 + assert(\is_callable($task)); 785 + $promise = \call_user_func($task); 786 + assert($promise instanceof PromiseInterface); 787 + $pending = $promise; 788 + 789 + $promise->then($taskCallback, array($deferred, 'reject')); 790 + }; 791 + 792 + $next(); 793 + 794 + /** @var PromiseInterface<array<T>> Remove once defining `Deferred()` above is supported by PHPStan, see https://github.com/phpstan/phpstan/issues/11032 */ 795 + return $deferred->promise(); 796 + } 797 + 798 + /** 799 + * @template T 800 + * @param iterable<(callable():(PromiseInterface<T>|T))|(callable(mixed):(PromiseInterface<T>|T))> $tasks 801 + * @return PromiseInterface<($tasks is non-empty-array|\Traversable ? T : null)> 802 + */ 803 + function waterfall(iterable $tasks): PromiseInterface 804 + { 805 + $pending = null; 806 + /** @var Deferred<T> $deferred*/ 807 + $deferred = new Deferred(function () use (&$pending) { 808 + /** @var ?PromiseInterface<T> $pending */ 809 + if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) { 810 + $pending->cancel(); 811 + } 812 + $pending = null; 813 + }); 814 + 815 + if ($tasks instanceof \IteratorAggregate) { 816 + $tasks = $tasks->getIterator(); 817 + assert($tasks instanceof \Iterator); 818 + } 819 + 820 + /** @var callable $next */ 821 + $next = function ($value = null) use (&$tasks, &$next, $deferred, &$pending) { 822 + if ($tasks instanceof \Iterator ? !$tasks->valid() : !$tasks) { 823 + $deferred->resolve($value); 824 + return; 825 + } 826 + 827 + if ($tasks instanceof \Iterator) { 828 + $task = $tasks->current(); 829 + $tasks->next(); 830 + } else { 831 + assert(\is_array($tasks)); 832 + $task = \array_shift($tasks); 833 + } 834 + 835 + assert(\is_callable($task)); 836 + $promise = \call_user_func_array($task, func_get_args()); 837 + assert($promise instanceof PromiseInterface); 838 + $pending = $promise; 839 + 840 + $promise->then($next, array($deferred, 'reject')); 841 + }; 842 + 843 + $next(); 844 + 845 + return $deferred->promise(); 846 + }
+9
vendor/react/async/src/functions_include.php
··· 1 + <?php 2 + 3 + namespace React\Async; 4 + 5 + // @codeCoverageIgnoreStart 6 + if (!\function_exists(__NAMESPACE__ . '\\parallel')) { 7 + require __DIR__ . '/functions.php'; 8 + } 9 + // @codeCoverageIgnoreEnd
+468
vendor/react/event-loop/CHANGELOG.md
··· 1 + # Changelog 2 + 3 + ## 1.5.0 (2023-11-13) 4 + 5 + * Feature: Improve performance by using `spl_object_id()` on PHP 7.2+. 6 + (#267 by @samsonasik) 7 + 8 + * Feature: Full PHP 8.3 compatibility. 9 + (#269 by @clue) 10 + 11 + * Update tests for `ext-uv` on PHP 8+ and legacy PHP. 12 + (#270 by @clue and #268 by @SimonFrings) 13 + 14 + ## 1.4.0 (2023-05-05) 15 + 16 + * Feature: Improve performance of `Loop` by avoiding unneeded method calls. 17 + (#266 by @clue) 18 + 19 + * Feature: Support checking `EINTR` constant from `ext-pcntl` without `ext-sockets`. 20 + (#265 by @clue) 21 + 22 + * Minor documentation improvements. 23 + (#254 by @nhedger) 24 + 25 + * Improve test suite, run tests on PHP 8.2 and report failed assertions. 26 + (#258 by @WyriHaximus, #264 by @clue and #251, #261 and #262 by @SimonFrings) 27 + 28 + ## 1.3.0 (2022-03-17) 29 + 30 + * Feature: Improve default `StreamSelectLoop` to report any warnings for invalid streams. 31 + (#245 by @clue) 32 + 33 + * Feature: Improve performance of `StreamSelectLoop` when no timers are scheduled. 34 + (#246 by @clue) 35 + 36 + * Fix: Fix periodic timer with zero interval for `ExtEvLoop` and legacy `ExtLibevLoop`. 37 + (#243 by @lucasnetau) 38 + 39 + * Minor documentation improvements, update PHP version references. 40 + (#240, #248 and #250 by @SimonFrings, #241 by @dbu and #249 by @clue) 41 + 42 + * Improve test suite and test against PHP 8.1. 43 + (#238 by @WyriHaximus and #242 by @clue) 44 + 45 + ## 1.2.0 (2021-07-11) 46 + 47 + A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop). 48 + 49 + * Feature: Introduce new concept of default loop with the new `Loop` class. 50 + (#226 by @WyriHaximus, #229, #231 and #232 by @clue) 51 + 52 + The `Loop` class exists as a convenient global accessor for the event loop. 53 + It provides all methods that exist on the `LoopInterface` as static methods and 54 + will automatically execute the loop at the end of the program: 55 + 56 + ```php 57 + $timer = Loop::addPeriodicTimer(0.1, function () { 58 + echo 'Tick' . PHP_EOL; 59 + }); 60 + 61 + Loop::addTimer(1.0, function () use ($timer) { 62 + Loop::cancelTimer($timer); 63 + echo 'Done' . PHP_EOL; 64 + }); 65 + ``` 66 + 67 + The explicit loop instructions are still valid and may still be useful in some applications, 68 + especially for a transition period towards the more concise style. 69 + The `Loop::get()` method can be used to get the currently active event loop instance. 70 + 71 + ```php 72 + // deprecated 73 + $loop = React\EventLoop\Factory::create(); 74 + 75 + // new 76 + $loop = React\EventLoop\Loop::get(); 77 + ``` 78 + 79 + * Minor documentation improvements and mark legacy extensions as deprecated. 80 + (#234 by @SimonFrings, #214 by @WyriHaximus and #233 and #235 by @nhedger) 81 + 82 + * Improve test suite, use GitHub actions for continuous integration (CI), 83 + update PHPUnit config and run tests on PHP 8. 84 + (#212 and #215 by @SimonFrings and #230 by @clue) 85 + 86 + ## 1.1.1 (2020-01-01) 87 + 88 + * Fix: Fix reporting connection refused errors with `ExtUvLoop` on Linux and `StreamSelectLoop` on Windows. 89 + (#207 and #208 by @clue) 90 + 91 + * Fix: Fix unsupported EventConfig and `SEGFAULT` on shutdown with `ExtEventLoop` on Windows. 92 + (#205 by @clue) 93 + 94 + * Fix: Prevent interval overflow for timers very far in the future with `ExtUvLoop`. 95 + (#196 by @PabloKowalczyk) 96 + 97 + * Fix: Check PCNTL functions for signal support instead of PCNTL extension with `StreamSelectLoop`. 98 + (#195 by @clue) 99 + 100 + * Add `.gitattributes` to exclude dev files from exports. 101 + (#201 by @reedy) 102 + 103 + * Improve test suite to fix testing `ExtUvLoop` on Travis, 104 + fix Travis CI builds, do not install `libuv` on legacy PHP setups, 105 + fix failing test cases due to inaccurate timers, 106 + run tests on Windows via Travis CI and 107 + run tests on PHP 7.4 and simplify test matrix and test setup. 108 + (#197 by @WyriHaximus and #202, #203, #204 and #209 by @clue) 109 + 110 + ## 1.1.0 (2019-02-07) 111 + 112 + * New UV based event loop (ext-uv). 113 + (#112 by @WyriHaximus) 114 + 115 + * Use high resolution timer on PHP 7.3+. 116 + (#182 by @clue) 117 + 118 + * Improve PCNTL signals by using async signal dispatching if available. 119 + (#179 by @CharlotteDunois) 120 + 121 + * Improve test suite and test suite set up. 122 + (#174 by @WyriHaximus, #181 by @clue) 123 + 124 + * Fix PCNTL signals edge case. 125 + (#183 by @clue) 126 + 127 + ## 1.0.0 (2018-07-11) 128 + 129 + * First stable LTS release, now following [SemVer](https://semver.org/). 130 + We'd like to emphasize that this component is production ready and battle-tested. 131 + We plan to support all long-term support (LTS) releases for at least 24 months, 132 + so you have a rock-solid foundation to build on top of. 133 + 134 + > Contains no other changes, so it's actually fully compatible with the v0.5.3 release. 135 + 136 + ## 0.5.3 (2018-07-09) 137 + 138 + * Improve performance by importing global functions. 139 + (#167 by @Ocramius) 140 + 141 + * Improve test suite by simplifying test bootstrap by using dev autoloader. 142 + (#169 by @lcobucci) 143 + 144 + * Minor internal changes to improved backward compatibility with PHP 5.3. 145 + (#166 by @Donatello-za) 146 + 147 + ## 0.5.2 (2018-04-24) 148 + 149 + * Feature: Improve memory consumption and runtime performance for `StreamSelectLoop` timers. 150 + (#164 by @clue) 151 + 152 + * Improve test suite by removing I/O dependency at `StreamSelectLoopTest` to fix Mac OS X tests. 153 + (#161 by @nawarian) 154 + 155 + ## 0.5.1 (2018-04-09) 156 + 157 + * Feature: New `ExtEvLoop` (PECL ext-ev) (#148 by @kaduev13) 158 + 159 + ## 0.5.0 (2018-04-05) 160 + 161 + A major feature release with a significant documentation overhaul and long overdue API cleanup! 162 + 163 + This update involves a number of BC breaks due to dropped support for deprecated 164 + functionality. We've tried hard to avoid BC breaks where possible and minimize 165 + impact otherwise. We expect that most consumers of this package will actually 166 + not be affected by any BC breaks, see below for more details. 167 + 168 + We realize that the changes listed below may seem overwhelming, but we've tried 169 + to be very clear about any possible BC breaks. Don't worry: In fact, all ReactPHP 170 + components are already compatible and support both this new release as well as 171 + providing backwards compatibility with the last release. 172 + 173 + * Feature / BC break: Add support for signal handling via new 174 + `LoopInterface::addSignal()` and `LoopInterface::removeSignal()` methods. 175 + (#104 by @WyriHaximus and #111 and #150 by @clue) 176 + 177 + ```php 178 + $loop->addSignal(SIGINT, function () { 179 + echo 'CTRL-C'; 180 + }); 181 + ``` 182 + 183 + * Feature: Significant documentation updates for `LoopInterface` and `Factory`. 184 + (#100, #119, #126, #127, #159 and #160 by @clue, #113 by @WyriHaximus and #81 and #91 by @jsor) 185 + 186 + * Feature: Add examples to ease getting started 187 + (#99, #100 and #125 by @clue, #59 by @WyriHaximus and #143 by @jsor) 188 + 189 + * Feature: Documentation for advanced timer concepts, such as monotonic time source vs wall-clock time 190 + and high precision timers with millisecond accuracy or below. 191 + (#130 and #157 by @clue) 192 + 193 + * Feature: Documentation for advanced stream concepts, such as edge-triggered event listeners 194 + and stream buffers and allow throwing Exception if stream resource is not supported. 195 + (#129 and #158 by @clue) 196 + 197 + * Feature: Throw `BadMethodCallException` on manual loop creation when required extension isn't installed. 198 + (#153 by @WyriHaximus) 199 + 200 + * Feature / BC break: First class support for legacy PHP 5.3 through PHP 7.2 and HHVM 201 + and remove all `callable` type hints for consistency reasons. 202 + (#141 and #151 by @clue) 203 + 204 + * BC break: Documentation for timer API and clean up unneeded timer API. 205 + (#102 by @clue) 206 + 207 + Remove `TimerInterface::cancel()`, use `LoopInterface::cancelTimer()` instead: 208 + 209 + ```php 210 + // old (method invoked on timer instance) 211 + $timer->cancel(); 212 + 213 + // already supported before: invoke method on loop instance 214 + $loop->cancelTimer($timer); 215 + ``` 216 + 217 + Remove unneeded `TimerInterface::setData()` and `TimerInterface::getData()`, 218 + use closure binding to add arbitrary data to timer instead: 219 + 220 + ```php 221 + // old (limited setData() and getData() only allows single variable) 222 + $name = 'Tester'; 223 + $timer = $loop->addTimer(1.0, function ($timer) { 224 + echo 'Hello ' . $timer->getData() . PHP_EOL; 225 + }); 226 + $timer->setData($name); 227 + 228 + // already supported before: closure binding allows any number of variables 229 + $name = 'Tester'; 230 + $loop->addTimer(1.0, function () use ($name) { 231 + echo 'Hello ' . $name . PHP_EOL; 232 + }); 233 + ``` 234 + 235 + Remove unneeded `TimerInterface::getLoop()`, use closure binding instead: 236 + 237 + ```php 238 + // old (getLoop() called on timer instance) 239 + $loop->addTimer(0.1, function ($timer) { 240 + $timer->getLoop()->stop(); 241 + }); 242 + 243 + // already supported before: use closure binding as usual 244 + $loop->addTimer(0.1, function () use ($loop) { 245 + $loop->stop(); 246 + }); 247 + ``` 248 + 249 + * BC break: Remove unneeded `LoopInterface::isTimerActive()` and 250 + `TimerInterface::isActive()` to reduce API surface. 251 + (#133 by @clue) 252 + 253 + ```php 254 + // old (method on timer instance or on loop instance) 255 + $timer->isActive(); 256 + $loop->isTimerActive($timer); 257 + ``` 258 + 259 + * BC break: Move `TimerInterface` one level up to `React\EventLoop\TimerInterface`. 260 + (#138 by @WyriHaximus) 261 + 262 + ```php 263 + // old (notice obsolete "Timer" namespace) 264 + assert($timer instanceof React\EventLoop\Timer\TimerInterface); 265 + 266 + // new 267 + assert($timer instanceof React\EventLoop\TimerInterface); 268 + ``` 269 + 270 + * BC break: Remove unneeded `LoopInterface::nextTick()` (and internal `NextTickQueue`), 271 + use `LoopInterface::futureTick()` instead. 272 + (#30 by @clue) 273 + 274 + ```php 275 + // old (removed) 276 + $loop->nextTick(function () { 277 + echo 'tick'; 278 + }); 279 + 280 + // already supported before 281 + $loop->futureTick(function () { 282 + echo 'tick'; 283 + }); 284 + ``` 285 + 286 + * BC break: Remove unneeded `$loop` argument for `LoopInterface::futureTick()` 287 + (and fix internal cyclic dependency). 288 + (#103 by @clue) 289 + 290 + ```php 291 + // old ($loop gets passed by default) 292 + $loop->futureTick(function ($loop) { 293 + $loop->stop(); 294 + }); 295 + 296 + // already supported before: use closure binding as usual 297 + $loop->futureTick(function () use ($loop) { 298 + $loop->stop(); 299 + }); 300 + ``` 301 + 302 + * BC break: Remove unneeded `LoopInterface::tick()`. 303 + (#72 by @jsor) 304 + 305 + ```php 306 + // old (removed) 307 + $loop->tick(); 308 + 309 + // suggested work around for testing purposes only 310 + $loop->futureTick(function () use ($loop) { 311 + $loop->stop(); 312 + }); 313 + ``` 314 + 315 + * BC break: Documentation for advanced stream API and clean up unneeded stream API. 316 + (#110 by @clue) 317 + 318 + Remove unneeded `$loop` argument for `LoopInterface::addReadStream()` 319 + and `LoopInterface::addWriteStream()`, use closure binding instead: 320 + 321 + ```php 322 + // old ($loop gets passed by default) 323 + $loop->addReadStream($stream, function ($stream, $loop) { 324 + $loop->removeReadStream($stream); 325 + }); 326 + 327 + // already supported before: use closure binding as usual 328 + $loop->addReadStream($stream, function ($stream) use ($loop) { 329 + $loop->removeReadStream($stream); 330 + }); 331 + ``` 332 + 333 + * BC break: Remove unneeded `LoopInterface::removeStream()` method, 334 + use `LoopInterface::removeReadStream()` and `LoopInterface::removeWriteStream()` instead. 335 + (#118 by @clue) 336 + 337 + ```php 338 + // old 339 + $loop->removeStream($stream); 340 + 341 + // already supported before 342 + $loop->removeReadStream($stream); 343 + $loop->removeWriteStream($stream); 344 + ``` 345 + 346 + * BC break: Rename `LibEventLoop` to `ExtLibeventLoop` and `LibEvLoop` to `ExtLibevLoop` 347 + for consistent naming for event loop implementations. 348 + (#128 by @clue) 349 + 350 + * BC break: Remove optional `EventBaseConfig` argument from `ExtEventLoop` 351 + and make its `FEATURE_FDS` enabled by default. 352 + (#156 by @WyriHaximus) 353 + 354 + * BC break: Mark all classes as final to discourage inheritance. 355 + (#131 by @clue) 356 + 357 + * Fix: Fix `ExtEventLoop` to keep track of stream resources (refcount) 358 + (#123 by @clue) 359 + 360 + * Fix: Ensure large timer interval does not overflow on 32bit systems 361 + (#132 by @clue) 362 + 363 + * Fix: Fix separately removing readable and writable side of stream when closing 364 + (#139 by @clue) 365 + 366 + * Fix: Properly clean up event watchers for `ext-event` and `ext-libev` 367 + (#149 by @clue) 368 + 369 + * Fix: Minor code cleanup and remove unneeded references 370 + (#145 by @seregazhuk) 371 + 372 + * Fix: Discourage outdated `ext-libevent` on PHP 7 373 + (#62 by @cboden) 374 + 375 + * Improve test suite by adding forward compatibility with PHPUnit 6 and PHPUnit 5, 376 + lock Travis distro so new defaults will not break the build, 377 + improve test suite to be less fragile and increase test timeouts, 378 + test against PHP 7.2 and reduce fwrite() call length to one chunk. 379 + (#106 and #144 by @clue, #120 and #124 by @carusogabriel, #147 by nawarian and #92 by @kelunik) 380 + 381 + * A number of changes were originally planned for this release but have been backported 382 + to the last `v0.4.3` already: #74, #76, #79, #81 (refs #65, #66, #67), #88 and #93 383 + 384 + ## 0.4.3 (2017-04-27) 385 + 386 + * Bug fix: Bugfix in the usage sample code #57 (@dandelionred) 387 + * Improvement: Remove branch-alias definition #53 (@WyriHaximus) 388 + * Improvement: StreamSelectLoop: Use fresh time so Timers added during stream events are accurate #51 (@andrewminerd) 389 + * Improvement: Avoid deprecation warnings in test suite due to deprecation of getMock() in PHPUnit #68 (@martinschroeder) 390 + * Improvement: Add PHPUnit 4.8 to require-dev #69 (@shaunbramley) 391 + * Improvement: Increase test timeouts for HHVM and unify timeout handling #70 (@clue) 392 + * Improvement: Travis improvements (backported from #74) #75 (@clue) 393 + * Improvement: Test suite now uses socket pairs instead of memory streams #66 (@martinschroeder) 394 + * Improvement: StreamSelectLoop: Test suite uses signal constant names in data provider #67 (@martinschroeder) 395 + * Improvement: ExtEventLoop: No longer suppress all errors #65 (@mamciek) 396 + * Improvement: Readme cleanup #89 (@jsor) 397 + * Improvement: Restructure and improve README #90 (@jsor) 398 + * Bug fix: StreamSelectLoop: Fix erroneous zero-time sleep (backport to 0.4) #94 (@jsor) 399 + 400 + ## 0.4.2 (2016-03-07) 401 + 402 + * Bug fix: No longer error when signals sent to StreamSelectLoop 403 + * Support HHVM and PHP7 (@ondrejmirtes, @cebe) 404 + * Feature: Added support for EventConfig for ExtEventLoop (@steverhoades) 405 + * Bug fix: Fixed an issue loading loop extension libs via autoloader (@czarpino) 406 + 407 + ## 0.4.1 (2014-04-13) 408 + 409 + * Bug fix: null timeout in StreamSelectLoop causing 100% CPU usage (@clue) 410 + * Bug fix: v0.3.4 changes merged for v0.4.1 411 + 412 + ## 0.4.0 (2014-02-02) 413 + 414 + * Feature: Added `EventLoopInterface::nextTick()`, implemented in all event loops (@jmalloc) 415 + * Feature: Added `EventLoopInterface::futureTick()`, implemented in all event loops (@jmalloc) 416 + * Feature: Added `ExtEventLoop` implementation using pecl/event (@jmalloc) 417 + * BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks 418 + * BC break: New method: `EventLoopInterface::nextTick()` 419 + * BC break: New method: `EventLoopInterface::futureTick()` 420 + * Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 421 + 422 + ## 0.3.5 (2016-12-28) 423 + 424 + This is a compatibility release that eases upgrading to the v0.4 release branch. 425 + You should consider upgrading to the v0.4 release branch. 426 + 427 + * Feature: Cap min timer interval at 1µs, thus improving compatibility with v0.4 428 + (#47 by @clue) 429 + 430 + ## 0.3.4 (2014-03-30) 431 + 432 + * Bug fix: Changed StreamSelectLoop to use non-blocking behavior on tick() (@astephens25) 433 + 434 + ## 0.3.3 (2013-07-08) 435 + 436 + * Bug fix: No error on removing non-existent streams (@clue) 437 + * Bug fix: Do not silently remove feof listeners in `LibEvLoop` 438 + 439 + ## 0.3.0 (2013-04-14) 440 + 441 + * BC break: New timers API (@nrk) 442 + * BC break: Remove check on return value from stream callbacks (@nrk) 443 + 444 + ## 0.2.7 (2013-01-05) 445 + 446 + * Bug fix: Fix libevent timers with PHP 5.3 447 + * Bug fix: Fix libevent timer cancellation (@nrk) 448 + 449 + ## 0.2.6 (2012-12-26) 450 + 451 + * Bug fix: Plug memory issue in libevent timers (@cameronjacobson) 452 + * Bug fix: Correctly pause LibEvLoop on stop() 453 + 454 + ## 0.2.3 (2012-11-14) 455 + 456 + * Feature: LibEvLoop, integration of `php-libev` 457 + 458 + ## 0.2.0 (2012-09-10) 459 + 460 + * Version bump 461 + 462 + ## 0.1.1 (2012-07-12) 463 + 464 + * Version bump 465 + 466 + ## 0.1.0 (2012-07-11) 467 + 468 + * First tagged release
+21
vendor/react/event-loop/LICENSE
··· 1 + The MIT License (MIT) 2 + 3 + Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is furnished 10 + to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 + THE SOFTWARE.
+930
vendor/react/event-loop/README.md
··· 1 + # EventLoop 2 + 3 + [![CI status](https://github.com/reactphp/event-loop/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/event-loop/actions) 4 + [![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) 5 + 6 + [ReactPHP](https://reactphp.org/)'s core reactor event loop that libraries can use for evented I/O. 7 + 8 + In order for async based libraries to be interoperable, they need to use the 9 + same event loop. This component provides a common `LoopInterface` that any 10 + library can target. This allows them to be used in the same loop, with one 11 + single [`run()`](#run) call that is controlled by the user. 12 + 13 + **Table of contents** 14 + 15 + * [Quickstart example](#quickstart-example) 16 + * [Usage](#usage) 17 + * [Loop](#loop) 18 + * [Loop methods](#loop-methods) 19 + * [Loop autorun](#loop-autorun) 20 + * [get()](#get) 21 + * [~~Factory~~](#factory) 22 + * [~~create()~~](#create) 23 + * [Loop implementations](#loop-implementations) 24 + * [StreamSelectLoop](#streamselectloop) 25 + * [ExtEventLoop](#exteventloop) 26 + * [ExtEvLoop](#extevloop) 27 + * [ExtUvLoop](#extuvloop) 28 + * [~~ExtLibeventLoop~~](#extlibeventloop) 29 + * [~~ExtLibevLoop~~](#extlibevloop) 30 + * [LoopInterface](#loopinterface) 31 + * [run()](#run) 32 + * [stop()](#stop) 33 + * [addTimer()](#addtimer) 34 + * [addPeriodicTimer()](#addperiodictimer) 35 + * [cancelTimer()](#canceltimer) 36 + * [futureTick()](#futuretick) 37 + * [addSignal()](#addsignal) 38 + * [removeSignal()](#removesignal) 39 + * [addReadStream()](#addreadstream) 40 + * [addWriteStream()](#addwritestream) 41 + * [removeReadStream()](#removereadstream) 42 + * [removeWriteStream()](#removewritestream) 43 + * [Install](#install) 44 + * [Tests](#tests) 45 + * [License](#license) 46 + * [More](#more) 47 + 48 + ## Quickstart example 49 + 50 + Here is an async HTTP server built with just the event loop. 51 + 52 + ```php 53 + <?php 54 + 55 + use React\EventLoop\Loop; 56 + 57 + require __DIR__ . '/vendor/autoload.php'; 58 + 59 + $server = stream_socket_server('tcp://127.0.0.1:8080'); 60 + stream_set_blocking($server, false); 61 + 62 + Loop::addReadStream($server, function ($server) { 63 + $conn = stream_socket_accept($server); 64 + $data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n"; 65 + Loop::addWriteStream($conn, function ($conn) use (&$data) { 66 + $written = fwrite($conn, $data); 67 + if ($written === strlen($data)) { 68 + fclose($conn); 69 + Loop::removeWriteStream($conn); 70 + } else { 71 + $data = substr($data, $written); 72 + } 73 + }); 74 + }); 75 + 76 + Loop::addPeriodicTimer(5, function () { 77 + $memory = memory_get_usage() / 1024; 78 + $formatted = number_format($memory, 3).'K'; 79 + echo "Current memory usage: {$formatted}\n"; 80 + }); 81 + ``` 82 + 83 + See also the [examples](examples). 84 + 85 + ## Usage 86 + 87 + Typical applications would use the [`Loop` class](#loop) to use the default 88 + event loop like this: 89 + 90 + ```php 91 + use React\EventLoop\Loop; 92 + 93 + $timer = Loop::addPeriodicTimer(0.1, function () { 94 + echo 'Tick' . PHP_EOL; 95 + }); 96 + 97 + Loop::addTimer(1.0, function () use ($timer) { 98 + Loop::cancelTimer($timer); 99 + echo 'Done' . PHP_EOL; 100 + }); 101 + ``` 102 + 103 + As an alternative, you can also explicitly create an event loop instance at the 104 + beginning, reuse it throughout your program and finally run it at the end of the 105 + program like this: 106 + 107 + ```php 108 + $loop = React\EventLoop\Loop::get(); // or deprecated React\EventLoop\Factory::create(); 109 + 110 + $timer = $loop->addPeriodicTimer(0.1, function () { 111 + echo 'Tick' . PHP_EOL; 112 + }); 113 + 114 + $loop->addTimer(1.0, function () use ($loop, $timer) { 115 + $loop->cancelTimer($timer); 116 + echo 'Done' . PHP_EOL; 117 + }); 118 + 119 + $loop->run(); 120 + ``` 121 + 122 + While the former is more concise, the latter is more explicit. 123 + In both cases, the program would perform the exact same steps. 124 + 125 + 1. The event loop instance is created at the beginning of the program. This is 126 + implicitly done the first time you call the [`Loop` class](#loop) or 127 + explicitly when using the deprecated [`Factory::create()` method](#create) 128 + (or manually instantiating any of the [loop implementations](#loop-implementations)). 129 + 2. The event loop is used directly or passed as an instance to library and 130 + application code. In this example, a periodic timer is registered with the 131 + event loop which simply outputs `Tick` every fraction of a second until another 132 + timer stops the periodic timer after a second. 133 + 3. The event loop is run at the end of the program. This is automatically done 134 + when using the [`Loop` class](#loop) or explicitly with a single [`run()`](#run) 135 + call at the end of the program. 136 + 137 + As of `v1.2.0`, we highly recommend using the [`Loop` class](#loop). 138 + The explicit loop instructions are still valid and may still be useful in some 139 + applications, especially for a transition period towards the more concise style. 140 + 141 + ### Loop 142 + 143 + The `Loop` class exists as a convenient global accessor for the event loop. 144 + 145 + #### Loop methods 146 + 147 + The `Loop` class provides all methods that exist on the [`LoopInterface`](#loopinterface) 148 + as static methods: 149 + 150 + * [run()](#run) 151 + * [stop()](#stop) 152 + * [addTimer()](#addtimer) 153 + * [addPeriodicTimer()](#addperiodictimer) 154 + * [cancelTimer()](#canceltimer) 155 + * [futureTick()](#futuretick) 156 + * [addSignal()](#addsignal) 157 + * [removeSignal()](#removesignal) 158 + * [addReadStream()](#addreadstream) 159 + * [addWriteStream()](#addwritestream) 160 + * [removeReadStream()](#removereadstream) 161 + * [removeWriteStream()](#removewritestream) 162 + 163 + If you're working with the event loop in your application code, it's often 164 + easiest to directly interface with the static methods defined on the `Loop` class 165 + like this: 166 + 167 + ```php 168 + use React\EventLoop\Loop; 169 + 170 + $timer = Loop::addPeriodicTimer(0.1, function () { 171 + echo 'Tick' . PHP_EOL; 172 + }); 173 + 174 + Loop::addTimer(1.0, function () use ($timer) { 175 + Loop::cancelTimer($timer); 176 + echo 'Done' . PHP_EOL; 177 + }); 178 + ``` 179 + 180 + On the other hand, if you're familiar with object-oriented programming (OOP) and 181 + dependency injection (DI), you may want to inject an event loop instance and 182 + invoke instance methods on the `LoopInterface` like this: 183 + 184 + ```php 185 + use React\EventLoop\Loop; 186 + use React\EventLoop\LoopInterface; 187 + 188 + class Greeter 189 + { 190 + private $loop; 191 + 192 + public function __construct(LoopInterface $loop) 193 + { 194 + $this->loop = $loop; 195 + } 196 + 197 + public function greet(string $name) 198 + { 199 + $this->loop->addTimer(1.0, function () use ($name) { 200 + echo 'Hello ' . $name . '!' . PHP_EOL; 201 + }); 202 + } 203 + } 204 + 205 + $greeter = new Greeter(Loop::get()); 206 + $greeter->greet('Alice'); 207 + $greeter->greet('Bob'); 208 + ``` 209 + 210 + Each static method call will be forwarded as-is to the underlying event loop 211 + instance by using the [`Loop::get()`](#get) call internally. 212 + See [`LoopInterface`](#loopinterface) for more details about available methods. 213 + 214 + #### Loop autorun 215 + 216 + When using the `Loop` class, it will automatically execute the loop at the end of 217 + the program. This means the following example will schedule a timer and will 218 + automatically execute the program until the timer event fires: 219 + 220 + ```php 221 + use React\EventLoop\Loop; 222 + 223 + Loop::addTimer(1.0, function () { 224 + echo 'Hello' . PHP_EOL; 225 + }); 226 + ``` 227 + 228 + As of `v1.2.0`, we highly recommend using the `Loop` class this way and omitting any 229 + explicit [`run()`](#run) calls. For BC reasons, the explicit [`run()`](#run) 230 + method is still valid and may still be useful in some applications, especially 231 + for a transition period towards the more concise style. 232 + 233 + If you don't want the `Loop` to run automatically, you can either explicitly 234 + [`run()`](#run) or [`stop()`](#stop) it. This can be useful if you're using 235 + a global exception handler like this: 236 + 237 + ```php 238 + use React\EventLoop\Loop; 239 + 240 + Loop::addTimer(10.0, function () { 241 + echo 'Never happens'; 242 + }); 243 + 244 + set_exception_handler(function (Throwable $e) { 245 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 246 + Loop::stop(); 247 + }); 248 + 249 + throw new RuntimeException('Demo'); 250 + ``` 251 + 252 + #### get() 253 + 254 + The `get(): LoopInterface` method can be used to 255 + get the currently active event loop instance. 256 + 257 + This method will always return the same event loop instance throughout the 258 + lifetime of your application. 259 + 260 + ```php 261 + use React\EventLoop\Loop; 262 + use React\EventLoop\LoopInterface; 263 + 264 + $loop = Loop::get(); 265 + 266 + assert($loop instanceof LoopInterface); 267 + assert($loop === Loop::get()); 268 + ``` 269 + 270 + This is particularly useful if you're using object-oriented programming (OOP) 271 + and dependency injection (DI). In this case, you may want to inject an event 272 + loop instance and invoke instance methods on the `LoopInterface` like this: 273 + 274 + ```php 275 + use React\EventLoop\Loop; 276 + use React\EventLoop\LoopInterface; 277 + 278 + class Greeter 279 + { 280 + private $loop; 281 + 282 + public function __construct(LoopInterface $loop) 283 + { 284 + $this->loop = $loop; 285 + } 286 + 287 + public function greet(string $name) 288 + { 289 + $this->loop->addTimer(1.0, function () use ($name) { 290 + echo 'Hello ' . $name . '!' . PHP_EOL; 291 + }); 292 + } 293 + } 294 + 295 + $greeter = new Greeter(Loop::get()); 296 + $greeter->greet('Alice'); 297 + $greeter->greet('Bob'); 298 + ``` 299 + 300 + See [`LoopInterface`](#loopinterface) for more details about available methods. 301 + 302 + ### ~~Factory~~ 303 + 304 + > Deprecated since v1.2.0, see [`Loop` class](#loop) instead. 305 + 306 + The deprecated `Factory` class exists as a convenient way to pick the best available 307 + [event loop implementation](#loop-implementations). 308 + 309 + #### ~~create()~~ 310 + 311 + > Deprecated since v1.2.0, see [`Loop::get()`](#get) instead. 312 + 313 + The deprecated `create(): LoopInterface` method can be used to 314 + create a new event loop instance: 315 + 316 + ```php 317 + // deprecated 318 + $loop = React\EventLoop\Factory::create(); 319 + 320 + // new 321 + $loop = React\EventLoop\Loop::get(); 322 + ``` 323 + 324 + This method always returns an instance implementing [`LoopInterface`](#loopinterface), 325 + the actual [event loop implementation](#loop-implementations) is an implementation detail. 326 + 327 + This method should usually only be called once at the beginning of the program. 328 + 329 + ### Loop implementations 330 + 331 + In addition to the [`LoopInterface`](#loopinterface), there are a number of 332 + event loop implementations provided. 333 + 334 + All of the event loops support these features: 335 + 336 + * File descriptor polling 337 + * One-off timers 338 + * Periodic timers 339 + * Deferred execution on future loop tick 340 + 341 + For most consumers of this package, the underlying event loop implementation is 342 + an implementation detail. 343 + You should use the [`Loop` class](#loop) to automatically create a new instance. 344 + 345 + Advanced! If you explicitly need a certain event loop implementation, you can 346 + manually instantiate one of the following classes. 347 + Note that you may have to install the required PHP extensions for the respective 348 + event loop implementation first or they will throw a `BadMethodCallException` on creation. 349 + 350 + #### StreamSelectLoop 351 + 352 + A `stream_select()` based event loop. 353 + 354 + This uses the [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php) 355 + function and is the only implementation that works out of the box with PHP. 356 + 357 + This event loop works out of the box on PHP 5.3 through PHP 8+ and HHVM. 358 + This means that no installation is required and this library works on all 359 + platforms and supported PHP versions. 360 + Accordingly, the [`Loop` class](#loop) and the deprecated [`Factory`](#factory) 361 + will use this event loop by default if you do not install any of the event loop 362 + extensions listed below. 363 + 364 + Under the hood, it does a simple `select` system call. 365 + This system call is limited to the maximum file descriptor number of 366 + `FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)` 367 + (`m` being the maximum file descriptor number passed). 368 + This means that you may run into issues when handling thousands of streams 369 + concurrently and you may want to look into using one of the alternative 370 + event loop implementations listed below in this case. 371 + If your use case is among the many common use cases that involve handling only 372 + dozens or a few hundred streams at once, then this event loop implementation 373 + performs really well. 374 + 375 + If you want to use signal handling (see also [`addSignal()`](#addsignal) below), 376 + this event loop implementation requires `ext-pcntl`. 377 + This extension is only available for Unix-like platforms and does not support 378 + Windows. 379 + It is commonly installed as part of many PHP distributions. 380 + If this extension is missing (or you're running on Windows), signal handling is 381 + not supported and throws a `BadMethodCallException` instead. 382 + 383 + This event loop is known to rely on wall-clock time to schedule future timers 384 + when using any version before PHP 7.3, because a monotonic time source is 385 + only available as of PHP 7.3 (`hrtime()`). 386 + While this does not affect many common use cases, this is an important 387 + distinction for programs that rely on a high time precision or on systems 388 + that are subject to discontinuous time adjustments (time jumps). 389 + This means that if you schedule a timer to trigger in 30s on PHP < 7.3 and 390 + then adjust your system time forward by 20s, the timer may trigger in 10s. 391 + See also [`addTimer()`](#addtimer) for more details. 392 + 393 + #### ExtEventLoop 394 + 395 + An `ext-event` based event loop. 396 + 397 + This uses the [`event` PECL extension](https://pecl.php.net/package/event), 398 + that provides an interface to `libevent` library. 399 + `libevent` itself supports a number of system-specific backends (epoll, kqueue). 400 + 401 + This loop is known to work with PHP 5.4 through PHP 8+. 402 + 403 + #### ExtEvLoop 404 + 405 + An `ext-ev` based event loop. 406 + 407 + This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev), 408 + that provides an interface to `libev` library. 409 + `libev` itself supports a number of system-specific backends (epoll, kqueue). 410 + 411 + 412 + This loop is known to work with PHP 5.4 through PHP 8+. 413 + 414 + #### ExtUvLoop 415 + 416 + An `ext-uv` based event loop. 417 + 418 + This loop uses the [`uv` PECL extension](https://pecl.php.net/package/uv), 419 + that provides an interface to `libuv` library. 420 + `libuv` itself supports a number of system-specific backends (epoll, kqueue). 421 + 422 + This loop is known to work with PHP 7+. 423 + 424 + #### ~~ExtLibeventLoop~~ 425 + 426 + > Deprecated since v1.2.0, use [`ExtEventLoop`](#exteventloop) instead. 427 + 428 + An `ext-libevent` based event loop. 429 + 430 + This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent), 431 + that provides an interface to `libevent` library. 432 + `libevent` itself supports a number of system-specific backends (epoll, kqueue). 433 + 434 + This event loop does only work with PHP 5. 435 + An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for 436 + PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s. 437 + To reiterate: Using this event loop on PHP 7 is not recommended. 438 + Accordingly, neither the [`Loop` class](#loop) nor the deprecated 439 + [`Factory` class](#factory) will try to use this event loop on PHP 7. 440 + 441 + This event loop is known to trigger a readable listener only if 442 + the stream *becomes* readable (edge-triggered) and may not trigger if the 443 + stream has already been readable from the beginning. 444 + This also implies that a stream may not be recognized as readable when data 445 + is still left in PHP's internal stream buffers. 446 + As such, it's recommended to use `stream_set_read_buffer($stream, 0);` 447 + to disable PHP's internal read buffer in this case. 448 + See also [`addReadStream()`](#addreadstream) for more details. 449 + 450 + #### ~~ExtLibevLoop~~ 451 + 452 + > Deprecated since v1.2.0, use [`ExtEvLoop`](#extevloop) instead. 453 + 454 + An `ext-libev` based event loop. 455 + 456 + This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev), 457 + that provides an interface to `libev` library. 458 + `libev` itself supports a number of system-specific backends (epoll, kqueue). 459 + 460 + This loop does only work with PHP 5. 461 + An update for PHP 7 is [unlikely](https://github.com/m4rw3r/php-libev/issues/8) 462 + to happen any time soon. 463 + 464 + ### LoopInterface 465 + 466 + #### run() 467 + 468 + The `run(): void` method can be used to 469 + run the event loop until there are no more tasks to perform. 470 + 471 + For many applications, this method is the only directly visible 472 + invocation on the event loop. 473 + As a rule of thumb, it is usually recommended to attach everything to the 474 + same loop instance and then run the loop once at the bottom end of the 475 + application. 476 + 477 + ```php 478 + $loop->run(); 479 + ``` 480 + 481 + This method will keep the loop running until there are no more tasks 482 + to perform. In other words: This method will block until the last 483 + timer, stream and/or signal has been removed. 484 + 485 + Likewise, it is imperative to ensure the application actually invokes 486 + this method once. Adding listeners to the loop and missing to actually 487 + run it will result in the application exiting without actually waiting 488 + for any of the attached listeners. 489 + 490 + This method MUST NOT be called while the loop is already running. 491 + This method MAY be called more than once after it has explicitly been 492 + [`stop()`ped](#stop) or after it automatically stopped because it 493 + previously did no longer have anything to do. 494 + 495 + #### stop() 496 + 497 + The `stop(): void` method can be used to 498 + instruct a running event loop to stop. 499 + 500 + This method is considered advanced usage and should be used with care. 501 + As a rule of thumb, it is usually recommended to let the loop stop 502 + only automatically when it no longer has anything to do. 503 + 504 + This method can be used to explicitly instruct the event loop to stop: 505 + 506 + ```php 507 + $loop->addTimer(3.0, function () use ($loop) { 508 + $loop->stop(); 509 + }); 510 + ``` 511 + 512 + Calling this method on a loop instance that is not currently running or 513 + on a loop instance that has already been stopped has no effect. 514 + 515 + #### addTimer() 516 + 517 + The `addTimer(float $interval, callable $callback): TimerInterface` method can be used to 518 + enqueue a callback to be invoked once after the given interval. 519 + 520 + The second parameter MUST be a timer callback function that accepts 521 + the timer instance as its only parameter. 522 + If you don't use the timer instance inside your timer callback function 523 + you MAY use a function which has no parameters at all. 524 + 525 + The timer callback function MUST NOT throw an `Exception`. 526 + The return value of the timer callback function will be ignored and has 527 + no effect, so for performance reasons you're recommended to not return 528 + any excessive data structures. 529 + 530 + This method returns a timer instance. The same timer instance will also be 531 + passed into the timer callback function as described above. 532 + You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer. 533 + Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure 534 + the callback will be invoked only once after the given interval. 535 + 536 + ```php 537 + $loop->addTimer(0.8, function () { 538 + echo 'world!' . PHP_EOL; 539 + }); 540 + 541 + $loop->addTimer(0.3, function () { 542 + echo 'hello '; 543 + }); 544 + ``` 545 + 546 + See also [example #1](examples). 547 + 548 + If you want to access any variables within your callback function, you 549 + can bind arbitrary data to a callback closure like this: 550 + 551 + ```php 552 + function hello($name, LoopInterface $loop) 553 + { 554 + $loop->addTimer(1.0, function () use ($name) { 555 + echo "hello $name\n"; 556 + }); 557 + } 558 + 559 + hello('Tester', $loop); 560 + ``` 561 + 562 + This interface does not enforce any particular timer resolution, so 563 + special care may have to be taken if you rely on very high precision with 564 + millisecond accuracy or below. Event loop implementations SHOULD work on 565 + a best effort basis and SHOULD provide at least millisecond accuracy 566 + unless otherwise noted. Many existing event loop implementations are 567 + known to provide microsecond accuracy, but it's generally not recommended 568 + to rely on this high precision. 569 + 570 + Similarly, the execution order of timers scheduled to execute at the 571 + same time (within its possible accuracy) is not guaranteed. 572 + 573 + This interface suggests that event loop implementations SHOULD use a 574 + monotonic time source if available. Given that a monotonic time source is 575 + only available as of PHP 7.3 by default, event loop implementations MAY 576 + fall back to using wall-clock time. 577 + While this does not affect many common use cases, this is an important 578 + distinction for programs that rely on a high time precision or on systems 579 + that are subject to discontinuous time adjustments (time jumps). 580 + This means that if you schedule a timer to trigger in 30s and then adjust 581 + your system time forward by 20s, the timer SHOULD still trigger in 30s. 582 + See also [event loop implementations](#loop-implementations) for more details. 583 + 584 + #### addPeriodicTimer() 585 + 586 + The `addPeriodicTimer(float $interval, callable $callback): TimerInterface` method can be used to 587 + enqueue a callback to be invoked repeatedly after the given interval. 588 + 589 + The second parameter MUST be a timer callback function that accepts 590 + the timer instance as its only parameter. 591 + If you don't use the timer instance inside your timer callback function 592 + you MAY use a function which has no parameters at all. 593 + 594 + The timer callback function MUST NOT throw an `Exception`. 595 + The return value of the timer callback function will be ignored and has 596 + no effect, so for performance reasons you're recommended to not return 597 + any excessive data structures. 598 + 599 + This method returns a timer instance. The same timer instance will also be 600 + passed into the timer callback function as described above. 601 + Unlike [`addTimer()`](#addtimer), this method will ensure the callback 602 + will be invoked infinitely after the given interval or until you invoke 603 + [`cancelTimer`](#canceltimer). 604 + 605 + ```php 606 + $timer = $loop->addPeriodicTimer(0.1, function () { 607 + echo 'tick!' . PHP_EOL; 608 + }); 609 + 610 + $loop->addTimer(1.0, function () use ($loop, $timer) { 611 + $loop->cancelTimer($timer); 612 + echo 'Done' . PHP_EOL; 613 + }); 614 + ``` 615 + 616 + See also [example #2](examples). 617 + 618 + If you want to limit the number of executions, you can bind 619 + arbitrary data to a callback closure like this: 620 + 621 + ```php 622 + function hello($name, LoopInterface $loop) 623 + { 624 + $n = 3; 625 + $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) { 626 + if ($n > 0) { 627 + --$n; 628 + echo "hello $name\n"; 629 + } else { 630 + $loop->cancelTimer($timer); 631 + } 632 + }); 633 + } 634 + 635 + hello('Tester', $loop); 636 + ``` 637 + 638 + This interface does not enforce any particular timer resolution, so 639 + special care may have to be taken if you rely on very high precision with 640 + millisecond accuracy or below. Event loop implementations SHOULD work on 641 + a best effort basis and SHOULD provide at least millisecond accuracy 642 + unless otherwise noted. Many existing event loop implementations are 643 + known to provide microsecond accuracy, but it's generally not recommended 644 + to rely on this high precision. 645 + 646 + Similarly, the execution order of timers scheduled to execute at the 647 + same time (within its possible accuracy) is not guaranteed. 648 + 649 + This interface suggests that event loop implementations SHOULD use a 650 + monotonic time source if available. Given that a monotonic time source is 651 + only available as of PHP 7.3 by default, event loop implementations MAY 652 + fall back to using wall-clock time. 653 + While this does not affect many common use cases, this is an important 654 + distinction for programs that rely on a high time precision or on systems 655 + that are subject to discontinuous time adjustments (time jumps). 656 + This means that if you schedule a timer to trigger in 30s and then adjust 657 + your system time forward by 20s, the timer SHOULD still trigger in 30s. 658 + See also [event loop implementations](#loop-implementations) for more details. 659 + 660 + Additionally, periodic timers may be subject to timer drift due to 661 + re-scheduling after each invocation. As such, it's generally not 662 + recommended to rely on this for high precision intervals with millisecond 663 + accuracy or below. 664 + 665 + #### cancelTimer() 666 + 667 + The `cancelTimer(TimerInterface $timer): void` method can be used to 668 + cancel a pending timer. 669 + 670 + See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples). 671 + 672 + Calling this method on a timer instance that has not been added to this 673 + loop instance or on a timer that has already been cancelled has no effect. 674 + 675 + #### futureTick() 676 + 677 + The `futureTick(callable $listener): void` method can be used to 678 + schedule a callback to be invoked on a future tick of the event loop. 679 + 680 + This works very much similar to timers with an interval of zero seconds, 681 + but does not require the overhead of scheduling a timer queue. 682 + 683 + The tick callback function MUST be able to accept zero parameters. 684 + 685 + The tick callback function MUST NOT throw an `Exception`. 686 + The return value of the tick callback function will be ignored and has 687 + no effect, so for performance reasons you're recommended to not return 688 + any excessive data structures. 689 + 690 + If you want to access any variables within your callback function, you 691 + can bind arbitrary data to a callback closure like this: 692 + 693 + ```php 694 + function hello($name, LoopInterface $loop) 695 + { 696 + $loop->futureTick(function () use ($name) { 697 + echo "hello $name\n"; 698 + }); 699 + } 700 + 701 + hello('Tester', $loop); 702 + ``` 703 + 704 + Unlike timers, tick callbacks are guaranteed to be executed in the order 705 + they are enqueued. 706 + Also, once a callback is enqueued, there's no way to cancel this operation. 707 + 708 + This is often used to break down bigger tasks into smaller steps (a form 709 + of cooperative multitasking). 710 + 711 + ```php 712 + $loop->futureTick(function () { 713 + echo 'b'; 714 + }); 715 + $loop->futureTick(function () { 716 + echo 'c'; 717 + }); 718 + echo 'a'; 719 + ``` 720 + 721 + See also [example #3](examples). 722 + 723 + #### addSignal() 724 + 725 + The `addSignal(int $signal, callable $listener): void` method can be used to 726 + register a listener to be notified when a signal has been caught by this process. 727 + 728 + This is useful to catch user interrupt signals or shutdown signals from 729 + tools like `supervisor` or `systemd`. 730 + 731 + The second parameter MUST be a listener callback function that accepts 732 + the signal as its only parameter. 733 + If you don't use the signal inside your listener callback function 734 + you MAY use a function which has no parameters at all. 735 + 736 + The listener callback function MUST NOT throw an `Exception`. 737 + The return value of the listener callback function will be ignored and has 738 + no effect, so for performance reasons you're recommended to not return 739 + any excessive data structures. 740 + 741 + ```php 742 + $loop->addSignal(SIGINT, function (int $signal) { 743 + echo 'Caught user interrupt signal' . PHP_EOL; 744 + }); 745 + ``` 746 + 747 + See also [example #4](examples). 748 + 749 + Signaling is only available on Unix-like platforms, Windows isn't 750 + supported due to operating system limitations. 751 + This method may throw a `BadMethodCallException` if signals aren't 752 + supported on this platform, for example when required extensions are 753 + missing. 754 + 755 + **Note: A listener can only be added once to the same signal, any 756 + attempts to add it more than once will be ignored.** 757 + 758 + #### removeSignal() 759 + 760 + The `removeSignal(int $signal, callable $listener): void` method can be used to 761 + remove a previously added signal listener. 762 + 763 + ```php 764 + $loop->removeSignal(SIGINT, $listener); 765 + ``` 766 + 767 + Any attempts to remove listeners that aren't registered will be ignored. 768 + 769 + #### addReadStream() 770 + 771 + > Advanced! Note that this low-level API is considered advanced usage. 772 + Most use cases should probably use the higher-level 773 + [readable Stream API](https://github.com/reactphp/stream#readablestreaminterface) 774 + instead. 775 + 776 + The `addReadStream(resource $stream, callable $callback): void` method can be used to 777 + register a listener to be notified when a stream is ready to read. 778 + 779 + The first parameter MUST be a valid stream resource that supports 780 + checking whether it is ready to read by this loop implementation. 781 + A single stream resource MUST NOT be added more than once. 782 + Instead, either call [`removeReadStream()`](#removereadstream) first or 783 + react to this event with a single listener and then dispatch from this 784 + listener. This method MAY throw an `Exception` if the given resource type 785 + is not supported by this loop implementation. 786 + 787 + The second parameter MUST be a listener callback function that accepts 788 + the stream resource as its only parameter. 789 + If you don't use the stream resource inside your listener callback function 790 + you MAY use a function which has no parameters at all. 791 + 792 + The listener callback function MUST NOT throw an `Exception`. 793 + The return value of the listener callback function will be ignored and has 794 + no effect, so for performance reasons you're recommended to not return 795 + any excessive data structures. 796 + 797 + If you want to access any variables within your callback function, you 798 + can bind arbitrary data to a callback closure like this: 799 + 800 + ```php 801 + $loop->addReadStream($stream, function ($stream) use ($name) { 802 + echo $name . ' said: ' . fread($stream); 803 + }); 804 + ``` 805 + 806 + See also [example #11](examples). 807 + 808 + You can invoke [`removeReadStream()`](#removereadstream) to remove the 809 + read event listener for this stream. 810 + 811 + The execution order of listeners when multiple streams become ready at 812 + the same time is not guaranteed. 813 + 814 + Some event loop implementations are known to only trigger the listener if 815 + the stream *becomes* readable (edge-triggered) and may not trigger if the 816 + stream has already been readable from the beginning. 817 + This also implies that a stream may not be recognized as readable when data 818 + is still left in PHP's internal stream buffers. 819 + As such, it's recommended to use `stream_set_read_buffer($stream, 0);` 820 + to disable PHP's internal read buffer in this case. 821 + 822 + #### addWriteStream() 823 + 824 + > Advanced! Note that this low-level API is considered advanced usage. 825 + Most use cases should probably use the higher-level 826 + [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface) 827 + instead. 828 + 829 + The `addWriteStream(resource $stream, callable $callback): void` method can be used to 830 + register a listener to be notified when a stream is ready to write. 831 + 832 + The first parameter MUST be a valid stream resource that supports 833 + checking whether it is ready to write by this loop implementation. 834 + A single stream resource MUST NOT be added more than once. 835 + Instead, either call [`removeWriteStream()`](#removewritestream) first or 836 + react to this event with a single listener and then dispatch from this 837 + listener. This method MAY throw an `Exception` if the given resource type 838 + is not supported by this loop implementation. 839 + 840 + The second parameter MUST be a listener callback function that accepts 841 + the stream resource as its only parameter. 842 + If you don't use the stream resource inside your listener callback function 843 + you MAY use a function which has no parameters at all. 844 + 845 + The listener callback function MUST NOT throw an `Exception`. 846 + The return value of the listener callback function will be ignored and has 847 + no effect, so for performance reasons you're recommended to not return 848 + any excessive data structures. 849 + 850 + If you want to access any variables within your callback function, you 851 + can bind arbitrary data to a callback closure like this: 852 + 853 + ```php 854 + $loop->addWriteStream($stream, function ($stream) use ($name) { 855 + fwrite($stream, 'Hello ' . $name); 856 + }); 857 + ``` 858 + 859 + See also [example #12](examples). 860 + 861 + You can invoke [`removeWriteStream()`](#removewritestream) to remove the 862 + write event listener for this stream. 863 + 864 + The execution order of listeners when multiple streams become ready at 865 + the same time is not guaranteed. 866 + 867 + #### removeReadStream() 868 + 869 + The `removeReadStream(resource $stream): void` method can be used to 870 + remove the read event listener for the given stream. 871 + 872 + Removing a stream from the loop that has already been removed or trying 873 + to remove a stream that was never added or is invalid has no effect. 874 + 875 + #### removeWriteStream() 876 + 877 + The `removeWriteStream(resource $stream): void` method can be used to 878 + remove the write event listener for the given stream. 879 + 880 + Removing a stream from the loop that has already been removed or trying 881 + to remove a stream that was never added or is invalid has no effect. 882 + 883 + ## Install 884 + 885 + The recommended way to install this library is [through Composer](https://getcomposer.org/). 886 + [New to Composer?](https://getcomposer.org/doc/00-intro.md) 887 + 888 + This project follows [SemVer](https://semver.org/). 889 + This will install the latest supported version: 890 + 891 + ```bash 892 + composer require react/event-loop:^1.5 893 + ``` 894 + 895 + See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. 896 + 897 + This project aims to run on any platform and thus does not require any PHP 898 + extensions and supports running on legacy PHP 5.3 through current PHP 8+ and 899 + HHVM. 900 + It's *highly recommended to use the latest supported PHP version* for this project. 901 + 902 + Installing any of the event loop extensions is suggested, but entirely optional. 903 + See also [event loop implementations](#loop-implementations) for more details. 904 + 905 + ## Tests 906 + 907 + To run the test suite, you first need to clone this repo and then install all 908 + dependencies [through Composer](https://getcomposer.org/): 909 + 910 + ```bash 911 + composer install 912 + ``` 913 + 914 + To run the test suite, go to the project root and run: 915 + 916 + ```bash 917 + vendor/bin/phpunit 918 + ``` 919 + 920 + ## License 921 + 922 + MIT, see [LICENSE file](LICENSE). 923 + 924 + ## More 925 + 926 + * See our [Stream component](https://github.com/reactphp/stream) for more 927 + information on how streams are used in real-world applications. 928 + * See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the 929 + [dependents on Packagist](https://packagist.org/packages/react/event-loop/dependents) 930 + for a list of packages that use the EventLoop in real-world applications.
+47
vendor/react/event-loop/composer.json
··· 1 + { 2 + "name": "react/event-loop", 3 + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", 4 + "keywords": ["event-loop", "asynchronous"], 5 + "license": "MIT", 6 + "authors": [ 7 + { 8 + "name": "Christian Lück", 9 + "homepage": "https://clue.engineering/", 10 + "email": "christian@clue.engineering" 11 + }, 12 + { 13 + "name": "Cees-Jan Kiewiet", 14 + "homepage": "https://wyrihaximus.net/", 15 + "email": "reactphp@ceesjankiewiet.nl" 16 + }, 17 + { 18 + "name": "Jan Sorgalla", 19 + "homepage": "https://sorgalla.com/", 20 + "email": "jsorgalla@gmail.com" 21 + }, 22 + { 23 + "name": "Chris Boden", 24 + "homepage": "https://cboden.dev/", 25 + "email": "cboden@gmail.com" 26 + } 27 + ], 28 + "require": { 29 + "php": ">=5.3.0" 30 + }, 31 + "require-dev": { 32 + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" 33 + }, 34 + "suggest": { 35 + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" 36 + }, 37 + "autoload": { 38 + "psr-4": { 39 + "React\\EventLoop\\": "src/" 40 + } 41 + }, 42 + "autoload-dev": { 43 + "psr-4": { 44 + "React\\Tests\\EventLoop\\": "tests/" 45 + } 46 + } 47 + }
+253
vendor/react/event-loop/src/ExtEvLoop.php
··· 1 + <?php 2 + 3 + namespace React\EventLoop; 4 + 5 + use Ev; 6 + use EvIo; 7 + use EvLoop; 8 + use React\EventLoop\Tick\FutureTickQueue; 9 + use React\EventLoop\Timer\Timer; 10 + use SplObjectStorage; 11 + 12 + /** 13 + * An `ext-ev` based event loop. 14 + * 15 + * This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev), 16 + * that provides an interface to `libev` library. 17 + * `libev` itself supports a number of system-specific backends (epoll, kqueue). 18 + * 19 + * This loop is known to work with PHP 5.4 through PHP 8+. 20 + * 21 + * @see http://php.net/manual/en/book.ev.php 22 + * @see https://bitbucket.org/osmanov/pecl-ev/overview 23 + */ 24 + class ExtEvLoop implements LoopInterface 25 + { 26 + /** 27 + * @var EvLoop 28 + */ 29 + private $loop; 30 + 31 + /** 32 + * @var FutureTickQueue 33 + */ 34 + private $futureTickQueue; 35 + 36 + /** 37 + * @var SplObjectStorage 38 + */ 39 + private $timers; 40 + 41 + /** 42 + * @var EvIo[] 43 + */ 44 + private $readStreams = array(); 45 + 46 + /** 47 + * @var EvIo[] 48 + */ 49 + private $writeStreams = array(); 50 + 51 + /** 52 + * @var bool 53 + */ 54 + private $running; 55 + 56 + /** 57 + * @var SignalsHandler 58 + */ 59 + private $signals; 60 + 61 + /** 62 + * @var \EvSignal[] 63 + */ 64 + private $signalEvents = array(); 65 + 66 + public function __construct() 67 + { 68 + $this->loop = new EvLoop(); 69 + $this->futureTickQueue = new FutureTickQueue(); 70 + $this->timers = new SplObjectStorage(); 71 + $this->signals = new SignalsHandler(); 72 + } 73 + 74 + public function addReadStream($stream, $listener) 75 + { 76 + $key = (int)$stream; 77 + 78 + if (isset($this->readStreams[$key])) { 79 + return; 80 + } 81 + 82 + $callback = $this->getStreamListenerClosure($stream, $listener); 83 + $event = $this->loop->io($stream, Ev::READ, $callback); 84 + $this->readStreams[$key] = $event; 85 + } 86 + 87 + /** 88 + * @param resource $stream 89 + * @param callable $listener 90 + * 91 + * @return \Closure 92 + */ 93 + private function getStreamListenerClosure($stream, $listener) 94 + { 95 + return function () use ($stream, $listener) { 96 + \call_user_func($listener, $stream); 97 + }; 98 + } 99 + 100 + public function addWriteStream($stream, $listener) 101 + { 102 + $key = (int)$stream; 103 + 104 + if (isset($this->writeStreams[$key])) { 105 + return; 106 + } 107 + 108 + $callback = $this->getStreamListenerClosure($stream, $listener); 109 + $event = $this->loop->io($stream, Ev::WRITE, $callback); 110 + $this->writeStreams[$key] = $event; 111 + } 112 + 113 + public function removeReadStream($stream) 114 + { 115 + $key = (int)$stream; 116 + 117 + if (!isset($this->readStreams[$key])) { 118 + return; 119 + } 120 + 121 + $this->readStreams[$key]->stop(); 122 + unset($this->readStreams[$key]); 123 + } 124 + 125 + public function removeWriteStream($stream) 126 + { 127 + $key = (int)$stream; 128 + 129 + if (!isset($this->writeStreams[$key])) { 130 + return; 131 + } 132 + 133 + $this->writeStreams[$key]->stop(); 134 + unset($this->writeStreams[$key]); 135 + } 136 + 137 + public function addTimer($interval, $callback) 138 + { 139 + $timer = new Timer($interval, $callback, false); 140 + 141 + $that = $this; 142 + $timers = $this->timers; 143 + $callback = function () use ($timer, $timers, $that) { 144 + \call_user_func($timer->getCallback(), $timer); 145 + 146 + if ($timers->contains($timer)) { 147 + $that->cancelTimer($timer); 148 + } 149 + }; 150 + 151 + $event = $this->loop->timer($timer->getInterval(), 0.0, $callback); 152 + $this->timers->attach($timer, $event); 153 + 154 + return $timer; 155 + } 156 + 157 + public function addPeriodicTimer($interval, $callback) 158 + { 159 + $timer = new Timer($interval, $callback, true); 160 + 161 + $callback = function () use ($timer) { 162 + \call_user_func($timer->getCallback(), $timer); 163 + }; 164 + 165 + $event = $this->loop->timer($timer->getInterval(), $timer->getInterval(), $callback); 166 + $this->timers->attach($timer, $event); 167 + 168 + return $timer; 169 + } 170 + 171 + public function cancelTimer(TimerInterface $timer) 172 + { 173 + if (!isset($this->timers[$timer])) { 174 + return; 175 + } 176 + 177 + $event = $this->timers[$timer]; 178 + $event->stop(); 179 + $this->timers->detach($timer); 180 + } 181 + 182 + public function futureTick($listener) 183 + { 184 + $this->futureTickQueue->add($listener); 185 + } 186 + 187 + public function run() 188 + { 189 + $this->running = true; 190 + 191 + while ($this->running) { 192 + $this->futureTickQueue->tick(); 193 + 194 + $hasPendingCallbacks = !$this->futureTickQueue->isEmpty(); 195 + $wasJustStopped = !$this->running; 196 + $nothingLeftToDo = !$this->readStreams 197 + && !$this->writeStreams 198 + && !$this->timers->count() 199 + && $this->signals->isEmpty(); 200 + 201 + $flags = Ev::RUN_ONCE; 202 + if ($wasJustStopped || $hasPendingCallbacks) { 203 + $flags |= Ev::RUN_NOWAIT; 204 + } elseif ($nothingLeftToDo) { 205 + break; 206 + } 207 + 208 + $this->loop->run($flags); 209 + } 210 + } 211 + 212 + public function stop() 213 + { 214 + $this->running = false; 215 + } 216 + 217 + public function __destruct() 218 + { 219 + /** @var TimerInterface $timer */ 220 + foreach ($this->timers as $timer) { 221 + $this->cancelTimer($timer); 222 + } 223 + 224 + foreach ($this->readStreams as $key => $stream) { 225 + $this->removeReadStream($key); 226 + } 227 + 228 + foreach ($this->writeStreams as $key => $stream) { 229 + $this->removeWriteStream($key); 230 + } 231 + } 232 + 233 + public function addSignal($signal, $listener) 234 + { 235 + $this->signals->add($signal, $listener); 236 + 237 + if (!isset($this->signalEvents[$signal])) { 238 + $this->signalEvents[$signal] = $this->loop->signal($signal, function() use ($signal) { 239 + $this->signals->call($signal); 240 + }); 241 + } 242 + } 243 + 244 + public function removeSignal($signal, $listener) 245 + { 246 + $this->signals->remove($signal, $listener); 247 + 248 + if (isset($this->signalEvents[$signal])) { 249 + $this->signalEvents[$signal]->stop(); 250 + unset($this->signalEvents[$signal]); 251 + } 252 + } 253 + }
+275
vendor/react/event-loop/src/ExtEventLoop.php
··· 1 + <?php 2 + 3 + namespace React\EventLoop; 4 + 5 + use BadMethodCallException; 6 + use Event; 7 + use EventBase; 8 + use React\EventLoop\Tick\FutureTickQueue; 9 + use React\EventLoop\Timer\Timer; 10 + use SplObjectStorage; 11 + 12 + /** 13 + * An `ext-event` based event loop. 14 + * 15 + * This uses the [`event` PECL extension](https://pecl.php.net/package/event), 16 + * that provides an interface to `libevent` library. 17 + * `libevent` itself supports a number of system-specific backends (epoll, kqueue). 18 + * 19 + * This loop is known to work with PHP 5.4 through PHP 8+. 20 + * 21 + * @link https://pecl.php.net/package/event 22 + */ 23 + final class ExtEventLoop implements LoopInterface 24 + { 25 + private $eventBase; 26 + private $futureTickQueue; 27 + private $timerCallback; 28 + private $timerEvents; 29 + private $streamCallback; 30 + private $readEvents = array(); 31 + private $writeEvents = array(); 32 + private $readListeners = array(); 33 + private $writeListeners = array(); 34 + private $readRefs = array(); 35 + private $writeRefs = array(); 36 + private $running; 37 + private $signals; 38 + private $signalEvents = array(); 39 + 40 + public function __construct() 41 + { 42 + if (!\class_exists('EventBase', false)) { 43 + throw new BadMethodCallException('Cannot create ExtEventLoop, ext-event extension missing'); 44 + } 45 + 46 + // support arbitrary file descriptors and not just sockets 47 + // Windows only has limited file descriptor support, so do not require this (will fail otherwise) 48 + // @link http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html#_setting_up_a_complicated_event_base 49 + $config = new \EventConfig(); 50 + if (\DIRECTORY_SEPARATOR !== '\\') { 51 + $config->requireFeatures(\EventConfig::FEATURE_FDS); 52 + } 53 + 54 + $this->eventBase = new EventBase($config); 55 + $this->futureTickQueue = new FutureTickQueue(); 56 + $this->timerEvents = new SplObjectStorage(); 57 + $this->signals = new SignalsHandler(); 58 + 59 + $this->createTimerCallback(); 60 + $this->createStreamCallback(); 61 + } 62 + 63 + public function __destruct() 64 + { 65 + // explicitly clear all references to Event objects to prevent SEGFAULTs on Windows 66 + foreach ($this->timerEvents as $timer) { 67 + $this->timerEvents->detach($timer); 68 + } 69 + 70 + $this->readEvents = array(); 71 + $this->writeEvents = array(); 72 + } 73 + 74 + public function addReadStream($stream, $listener) 75 + { 76 + $key = (int) $stream; 77 + if (isset($this->readListeners[$key])) { 78 + return; 79 + } 80 + 81 + $event = new Event($this->eventBase, $stream, Event::PERSIST | Event::READ, $this->streamCallback); 82 + $event->add(); 83 + $this->readEvents[$key] = $event; 84 + $this->readListeners[$key] = $listener; 85 + 86 + // ext-event does not increase refcount on stream resources for PHP 7+ 87 + // manually keep track of stream resource to prevent premature garbage collection 88 + if (\PHP_VERSION_ID >= 70000) { 89 + $this->readRefs[$key] = $stream; 90 + } 91 + } 92 + 93 + public function addWriteStream($stream, $listener) 94 + { 95 + $key = (int) $stream; 96 + if (isset($this->writeListeners[$key])) { 97 + return; 98 + } 99 + 100 + $event = new Event($this->eventBase, $stream, Event::PERSIST | Event::WRITE, $this->streamCallback); 101 + $event->add(); 102 + $this->writeEvents[$key] = $event; 103 + $this->writeListeners[$key] = $listener; 104 + 105 + // ext-event does not increase refcount on stream resources for PHP 7+ 106 + // manually keep track of stream resource to prevent premature garbage collection 107 + if (\PHP_VERSION_ID >= 70000) { 108 + $this->writeRefs[$key] = $stream; 109 + } 110 + } 111 + 112 + public function removeReadStream($stream) 113 + { 114 + $key = (int) $stream; 115 + 116 + if (isset($this->readEvents[$key])) { 117 + $this->readEvents[$key]->free(); 118 + unset( 119 + $this->readEvents[$key], 120 + $this->readListeners[$key], 121 + $this->readRefs[$key] 122 + ); 123 + } 124 + } 125 + 126 + public function removeWriteStream($stream) 127 + { 128 + $key = (int) $stream; 129 + 130 + if (isset($this->writeEvents[$key])) { 131 + $this->writeEvents[$key]->free(); 132 + unset( 133 + $this->writeEvents[$key], 134 + $this->writeListeners[$key], 135 + $this->writeRefs[$key] 136 + ); 137 + } 138 + } 139 + 140 + public function addTimer($interval, $callback) 141 + { 142 + $timer = new Timer($interval, $callback, false); 143 + 144 + $this->scheduleTimer($timer); 145 + 146 + return $timer; 147 + } 148 + 149 + public function addPeriodicTimer($interval, $callback) 150 + { 151 + $timer = new Timer($interval, $callback, true); 152 + 153 + $this->scheduleTimer($timer); 154 + 155 + return $timer; 156 + } 157 + 158 + public function cancelTimer(TimerInterface $timer) 159 + { 160 + if ($this->timerEvents->contains($timer)) { 161 + $this->timerEvents[$timer]->free(); 162 + $this->timerEvents->detach($timer); 163 + } 164 + } 165 + 166 + public function futureTick($listener) 167 + { 168 + $this->futureTickQueue->add($listener); 169 + } 170 + 171 + public function addSignal($signal, $listener) 172 + { 173 + $this->signals->add($signal, $listener); 174 + 175 + if (!isset($this->signalEvents[$signal])) { 176 + $this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, array($this->signals, 'call')); 177 + $this->signalEvents[$signal]->add(); 178 + } 179 + } 180 + 181 + public function removeSignal($signal, $listener) 182 + { 183 + $this->signals->remove($signal, $listener); 184 + 185 + if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) { 186 + $this->signalEvents[$signal]->free(); 187 + unset($this->signalEvents[$signal]); 188 + } 189 + } 190 + 191 + public function run() 192 + { 193 + $this->running = true; 194 + 195 + while ($this->running) { 196 + $this->futureTickQueue->tick(); 197 + 198 + $flags = EventBase::LOOP_ONCE; 199 + if (!$this->running || !$this->futureTickQueue->isEmpty()) { 200 + $flags |= EventBase::LOOP_NONBLOCK; 201 + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) { 202 + break; 203 + } 204 + 205 + $this->eventBase->loop($flags); 206 + } 207 + } 208 + 209 + public function stop() 210 + { 211 + $this->running = false; 212 + } 213 + 214 + /** 215 + * Schedule a timer for execution. 216 + * 217 + * @param TimerInterface $timer 218 + */ 219 + private function scheduleTimer(TimerInterface $timer) 220 + { 221 + $flags = Event::TIMEOUT; 222 + 223 + if ($timer->isPeriodic()) { 224 + $flags |= Event::PERSIST; 225 + } 226 + 227 + $event = new Event($this->eventBase, -1, $flags, $this->timerCallback, $timer); 228 + $this->timerEvents[$timer] = $event; 229 + 230 + $event->add($timer->getInterval()); 231 + } 232 + 233 + /** 234 + * Create a callback used as the target of timer events. 235 + * 236 + * A reference is kept to the callback for the lifetime of the loop 237 + * to prevent "Cannot destroy active lambda function" fatal error from 238 + * the event extension. 239 + */ 240 + private function createTimerCallback() 241 + { 242 + $timers = $this->timerEvents; 243 + $this->timerCallback = function ($_, $__, $timer) use ($timers) { 244 + \call_user_func($timer->getCallback(), $timer); 245 + 246 + if (!$timer->isPeriodic() && $timers->contains($timer)) { 247 + $this->cancelTimer($timer); 248 + } 249 + }; 250 + } 251 + 252 + /** 253 + * Create a callback used as the target of stream events. 254 + * 255 + * A reference is kept to the callback for the lifetime of the loop 256 + * to prevent "Cannot destroy active lambda function" fatal error from 257 + * the event extension. 258 + */ 259 + private function createStreamCallback() 260 + { 261 + $read =& $this->readListeners; 262 + $write =& $this->writeListeners; 263 + $this->streamCallback = function ($stream, $flags) use (&$read, &$write) { 264 + $key = (int) $stream; 265 + 266 + if (Event::READ === (Event::READ & $flags) && isset($read[$key])) { 267 + \call_user_func($read[$key], $stream); 268 + } 269 + 270 + if (Event::WRITE === (Event::WRITE & $flags) && isset($write[$key])) { 271 + \call_user_func($write[$key], $stream); 272 + } 273 + }; 274 + } 275 + }
+201
vendor/react/event-loop/src/ExtLibevLoop.php
··· 1 + <?php 2 + 3 + namespace React\EventLoop; 4 + 5 + use BadMethodCallException; 6 + use libev\EventLoop; 7 + use libev\IOEvent; 8 + use libev\SignalEvent; 9 + use libev\TimerEvent; 10 + use React\EventLoop\Tick\FutureTickQueue; 11 + use React\EventLoop\Timer\Timer; 12 + use SplObjectStorage; 13 + 14 + /** 15 + * [Deprecated] An `ext-libev` based event loop. 16 + * 17 + * This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev), 18 + * that provides an interface to `libev` library. 19 + * `libev` itself supports a number of system-specific backends (epoll, kqueue). 20 + * 21 + * This loop does only work with PHP 5. 22 + * An update for PHP 7 is [unlikely](https://github.com/m4rw3r/php-libev/issues/8) 23 + * to happen any time soon. 24 + * 25 + * @see https://github.com/m4rw3r/php-libev 26 + * @see https://gist.github.com/1688204 27 + * @deprecated 1.2.0, use [`ExtEvLoop`](#extevloop) instead. 28 + */ 29 + final class ExtLibevLoop implements LoopInterface 30 + { 31 + private $loop; 32 + private $futureTickQueue; 33 + private $timerEvents; 34 + private $readEvents = array(); 35 + private $writeEvents = array(); 36 + private $running; 37 + private $signals; 38 + private $signalEvents = array(); 39 + 40 + public function __construct() 41 + { 42 + if (!\class_exists('libev\EventLoop', false)) { 43 + throw new BadMethodCallException('Cannot create ExtLibevLoop, ext-libev extension missing'); 44 + } 45 + 46 + $this->loop = new EventLoop(); 47 + $this->futureTickQueue = new FutureTickQueue(); 48 + $this->timerEvents = new SplObjectStorage(); 49 + $this->signals = new SignalsHandler(); 50 + } 51 + 52 + public function addReadStream($stream, $listener) 53 + { 54 + if (isset($this->readEvents[(int) $stream])) { 55 + return; 56 + } 57 + 58 + $callback = function () use ($stream, $listener) { 59 + \call_user_func($listener, $stream); 60 + }; 61 + 62 + $event = new IOEvent($callback, $stream, IOEvent::READ); 63 + $this->loop->add($event); 64 + 65 + $this->readEvents[(int) $stream] = $event; 66 + } 67 + 68 + public function addWriteStream($stream, $listener) 69 + { 70 + if (isset($this->writeEvents[(int) $stream])) { 71 + return; 72 + } 73 + 74 + $callback = function () use ($stream, $listener) { 75 + \call_user_func($listener, $stream); 76 + }; 77 + 78 + $event = new IOEvent($callback, $stream, IOEvent::WRITE); 79 + $this->loop->add($event); 80 + 81 + $this->writeEvents[(int) $stream] = $event; 82 + } 83 + 84 + public function removeReadStream($stream) 85 + { 86 + $key = (int) $stream; 87 + 88 + if (isset($this->readEvents[$key])) { 89 + $this->readEvents[$key]->stop(); 90 + $this->loop->remove($this->readEvents[$key]); 91 + unset($this->readEvents[$key]); 92 + } 93 + } 94 + 95 + public function removeWriteStream($stream) 96 + { 97 + $key = (int) $stream; 98 + 99 + if (isset($this->writeEvents[$key])) { 100 + $this->writeEvents[$key]->stop(); 101 + $this->loop->remove($this->writeEvents[$key]); 102 + unset($this->writeEvents[$key]); 103 + } 104 + } 105 + 106 + public function addTimer($interval, $callback) 107 + { 108 + $timer = new Timer( $interval, $callback, false); 109 + 110 + $that = $this; 111 + $timers = $this->timerEvents; 112 + $callback = function () use ($timer, $timers, $that) { 113 + \call_user_func($timer->getCallback(), $timer); 114 + 115 + if ($timers->contains($timer)) { 116 + $that->cancelTimer($timer); 117 + } 118 + }; 119 + 120 + $event = new TimerEvent($callback, $timer->getInterval()); 121 + $this->timerEvents->attach($timer, $event); 122 + $this->loop->add($event); 123 + 124 + return $timer; 125 + } 126 + 127 + public function addPeriodicTimer($interval, $callback) 128 + { 129 + $timer = new Timer($interval, $callback, true); 130 + 131 + $callback = function () use ($timer) { 132 + \call_user_func($timer->getCallback(), $timer); 133 + }; 134 + 135 + $event = new TimerEvent($callback, $timer->getInterval(), $timer->getInterval()); 136 + $this->timerEvents->attach($timer, $event); 137 + $this->loop->add($event); 138 + 139 + return $timer; 140 + } 141 + 142 + public function cancelTimer(TimerInterface $timer) 143 + { 144 + if (isset($this->timerEvents[$timer])) { 145 + $this->loop->remove($this->timerEvents[$timer]); 146 + $this->timerEvents->detach($timer); 147 + } 148 + } 149 + 150 + public function futureTick($listener) 151 + { 152 + $this->futureTickQueue->add($listener); 153 + } 154 + 155 + public function addSignal($signal, $listener) 156 + { 157 + $this->signals->add($signal, $listener); 158 + 159 + if (!isset($this->signalEvents[$signal])) { 160 + $signals = $this->signals; 161 + $this->signalEvents[$signal] = new SignalEvent(function () use ($signals, $signal) { 162 + $signals->call($signal); 163 + }, $signal); 164 + $this->loop->add($this->signalEvents[$signal]); 165 + } 166 + } 167 + 168 + public function removeSignal($signal, $listener) 169 + { 170 + $this->signals->remove($signal, $listener); 171 + 172 + if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) { 173 + $this->signalEvents[$signal]->stop(); 174 + $this->loop->remove($this->signalEvents[$signal]); 175 + unset($this->signalEvents[$signal]); 176 + } 177 + } 178 + 179 + public function run() 180 + { 181 + $this->running = true; 182 + 183 + while ($this->running) { 184 + $this->futureTickQueue->tick(); 185 + 186 + $flags = EventLoop::RUN_ONCE; 187 + if (!$this->running || !$this->futureTickQueue->isEmpty()) { 188 + $flags |= EventLoop::RUN_NOWAIT; 189 + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) { 190 + break; 191 + } 192 + 193 + $this->loop->run($flags); 194 + } 195 + } 196 + 197 + public function stop() 198 + { 199 + $this->running = false; 200 + } 201 + }
+285
vendor/react/event-loop/src/ExtLibeventLoop.php
··· 1 + <?php 2 + 3 + namespace React\EventLoop; 4 + 5 + use BadMethodCallException; 6 + use Event; 7 + use EventBase; 8 + use React\EventLoop\Tick\FutureTickQueue; 9 + use React\EventLoop\Timer\Timer; 10 + use SplObjectStorage; 11 + 12 + /** 13 + * [Deprecated] An `ext-libevent` based event loop. 14 + * 15 + * This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent), 16 + * that provides an interface to `libevent` library. 17 + * `libevent` itself supports a number of system-specific backends (epoll, kqueue). 18 + * 19 + * This event loop does only work with PHP 5. 20 + * An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for 21 + * PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s. 22 + * To reiterate: Using this event loop on PHP 7 is not recommended. 23 + * Accordingly, neither the [`Loop` class](#loop) nor the deprecated 24 + * [`Factory` class](#factory) will try to use this event loop on PHP 7. 25 + * 26 + * This event loop is known to trigger a readable listener only if 27 + * the stream *becomes* readable (edge-triggered) and may not trigger if the 28 + * stream has already been readable from the beginning. 29 + * This also implies that a stream may not be recognized as readable when data 30 + * is still left in PHP's internal stream buffers. 31 + * As such, it's recommended to use `stream_set_read_buffer($stream, 0);` 32 + * to disable PHP's internal read buffer in this case. 33 + * See also [`addReadStream()`](#addreadstream) for more details. 34 + * 35 + * @link https://pecl.php.net/package/libevent 36 + * @deprecated 1.2.0, use [`ExtEventLoop`](#exteventloop) instead. 37 + */ 38 + final class ExtLibeventLoop implements LoopInterface 39 + { 40 + /** @internal */ 41 + const MICROSECONDS_PER_SECOND = 1000000; 42 + 43 + private $eventBase; 44 + private $futureTickQueue; 45 + private $timerCallback; 46 + private $timerEvents; 47 + private $streamCallback; 48 + private $readEvents = array(); 49 + private $writeEvents = array(); 50 + private $readListeners = array(); 51 + private $writeListeners = array(); 52 + private $running; 53 + private $signals; 54 + private $signalEvents = array(); 55 + 56 + public function __construct() 57 + { 58 + if (!\function_exists('event_base_new')) { 59 + throw new BadMethodCallException('Cannot create ExtLibeventLoop, ext-libevent extension missing'); 60 + } 61 + 62 + $this->eventBase = \event_base_new(); 63 + $this->futureTickQueue = new FutureTickQueue(); 64 + $this->timerEvents = new SplObjectStorage(); 65 + $this->signals = new SignalsHandler(); 66 + 67 + $this->createTimerCallback(); 68 + $this->createStreamCallback(); 69 + } 70 + 71 + public function addReadStream($stream, $listener) 72 + { 73 + $key = (int) $stream; 74 + if (isset($this->readListeners[$key])) { 75 + return; 76 + } 77 + 78 + $event = \event_new(); 79 + \event_set($event, $stream, \EV_PERSIST | \EV_READ, $this->streamCallback); 80 + \event_base_set($event, $this->eventBase); 81 + \event_add($event); 82 + 83 + $this->readEvents[$key] = $event; 84 + $this->readListeners[$key] = $listener; 85 + } 86 + 87 + public function addWriteStream($stream, $listener) 88 + { 89 + $key = (int) $stream; 90 + if (isset($this->writeListeners[$key])) { 91 + return; 92 + } 93 + 94 + $event = \event_new(); 95 + \event_set($event, $stream, \EV_PERSIST | \EV_WRITE, $this->streamCallback); 96 + \event_base_set($event, $this->eventBase); 97 + \event_add($event); 98 + 99 + $this->writeEvents[$key] = $event; 100 + $this->writeListeners[$key] = $listener; 101 + } 102 + 103 + public function removeReadStream($stream) 104 + { 105 + $key = (int) $stream; 106 + 107 + if (isset($this->readListeners[$key])) { 108 + $event = $this->readEvents[$key]; 109 + \event_del($event); 110 + \event_free($event); 111 + 112 + unset( 113 + $this->readEvents[$key], 114 + $this->readListeners[$key] 115 + ); 116 + } 117 + } 118 + 119 + public function removeWriteStream($stream) 120 + { 121 + $key = (int) $stream; 122 + 123 + if (isset($this->writeListeners[$key])) { 124 + $event = $this->writeEvents[$key]; 125 + \event_del($event); 126 + \event_free($event); 127 + 128 + unset( 129 + $this->writeEvents[$key], 130 + $this->writeListeners[$key] 131 + ); 132 + } 133 + } 134 + 135 + public function addTimer($interval, $callback) 136 + { 137 + $timer = new Timer($interval, $callback, false); 138 + 139 + $this->scheduleTimer($timer); 140 + 141 + return $timer; 142 + } 143 + 144 + public function addPeriodicTimer($interval, $callback) 145 + { 146 + $timer = new Timer($interval, $callback, true); 147 + 148 + $this->scheduleTimer($timer); 149 + 150 + return $timer; 151 + } 152 + 153 + public function cancelTimer(TimerInterface $timer) 154 + { 155 + if ($this->timerEvents->contains($timer)) { 156 + $event = $this->timerEvents[$timer]; 157 + \event_del($event); 158 + \event_free($event); 159 + 160 + $this->timerEvents->detach($timer); 161 + } 162 + } 163 + 164 + public function futureTick($listener) 165 + { 166 + $this->futureTickQueue->add($listener); 167 + } 168 + 169 + public function addSignal($signal, $listener) 170 + { 171 + $this->signals->add($signal, $listener); 172 + 173 + if (!isset($this->signalEvents[$signal])) { 174 + $this->signalEvents[$signal] = \event_new(); 175 + \event_set($this->signalEvents[$signal], $signal, \EV_PERSIST | \EV_SIGNAL, array($this->signals, 'call')); 176 + \event_base_set($this->signalEvents[$signal], $this->eventBase); 177 + \event_add($this->signalEvents[$signal]); 178 + } 179 + } 180 + 181 + public function removeSignal($signal, $listener) 182 + { 183 + $this->signals->remove($signal, $listener); 184 + 185 + if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) { 186 + \event_del($this->signalEvents[$signal]); 187 + \event_free($this->signalEvents[$signal]); 188 + unset($this->signalEvents[$signal]); 189 + } 190 + } 191 + 192 + public function run() 193 + { 194 + $this->running = true; 195 + 196 + while ($this->running) { 197 + $this->futureTickQueue->tick(); 198 + 199 + $flags = \EVLOOP_ONCE; 200 + if (!$this->running || !$this->futureTickQueue->isEmpty()) { 201 + $flags |= \EVLOOP_NONBLOCK; 202 + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) { 203 + break; 204 + } 205 + 206 + \event_base_loop($this->eventBase, $flags); 207 + } 208 + } 209 + 210 + public function stop() 211 + { 212 + $this->running = false; 213 + } 214 + 215 + /** 216 + * Schedule a timer for execution. 217 + * 218 + * @param TimerInterface $timer 219 + */ 220 + private function scheduleTimer(TimerInterface $timer) 221 + { 222 + $this->timerEvents[$timer] = $event = \event_timer_new(); 223 + 224 + \event_timer_set($event, $this->timerCallback, $timer); 225 + \event_base_set($event, $this->eventBase); 226 + \event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND); 227 + } 228 + 229 + /** 230 + * Create a callback used as the target of timer events. 231 + * 232 + * A reference is kept to the callback for the lifetime of the loop 233 + * to prevent "Cannot destroy active lambda function" fatal error from 234 + * the event extension. 235 + */ 236 + private function createTimerCallback() 237 + { 238 + $that = $this; 239 + $timers = $this->timerEvents; 240 + $this->timerCallback = function ($_, $__, $timer) use ($timers, $that) { 241 + \call_user_func($timer->getCallback(), $timer); 242 + 243 + // Timer already cancelled ... 244 + if (!$timers->contains($timer)) { 245 + return; 246 + } 247 + 248 + // Reschedule periodic timers ... 249 + if ($timer->isPeriodic()) { 250 + \event_add( 251 + $timers[$timer], 252 + $timer->getInterval() * ExtLibeventLoop::MICROSECONDS_PER_SECOND 253 + ); 254 + 255 + // Clean-up one shot timers ... 256 + } else { 257 + $that->cancelTimer($timer); 258 + } 259 + }; 260 + } 261 + 262 + /** 263 + * Create a callback used as the target of stream events. 264 + * 265 + * A reference is kept to the callback for the lifetime of the loop 266 + * to prevent "Cannot destroy active lambda function" fatal error from 267 + * the event extension. 268 + */ 269 + private function createStreamCallback() 270 + { 271 + $read =& $this->readListeners; 272 + $write =& $this->writeListeners; 273 + $this->streamCallback = function ($stream, $flags) use (&$read, &$write) { 274 + $key = (int) $stream; 275 + 276 + if (\EV_READ === (\EV_READ & $flags) && isset($read[$key])) { 277 + \call_user_func($read[$key], $stream); 278 + } 279 + 280 + if (\EV_WRITE === (\EV_WRITE & $flags) && isset($write[$key])) { 281 + \call_user_func($write[$key], $stream); 282 + } 283 + }; 284 + } 285 + }
+342
vendor/react/event-loop/src/ExtUvLoop.php
··· 1 + <?php 2 + 3 + namespace React\EventLoop; 4 + 5 + use React\EventLoop\Tick\FutureTickQueue; 6 + use React\EventLoop\Timer\Timer; 7 + use SplObjectStorage; 8 + 9 + /** 10 + * An `ext-uv` based event loop. 11 + * 12 + * This loop uses the [`uv` PECL extension](https://pecl.php.net/package/uv), 13 + * that provides an interface to `libuv` library. 14 + * `libuv` itself supports a number of system-specific backends (epoll, kqueue). 15 + * 16 + * This loop is known to work with PHP 7+. 17 + * 18 + * @see https://github.com/bwoebi/php-uv 19 + */ 20 + final class ExtUvLoop implements LoopInterface 21 + { 22 + private $uv; 23 + private $futureTickQueue; 24 + private $timers; 25 + private $streamEvents = array(); 26 + private $readStreams = array(); 27 + private $writeStreams = array(); 28 + private $running; 29 + private $signals; 30 + private $signalEvents = array(); 31 + private $streamListener; 32 + 33 + public function __construct() 34 + { 35 + if (!\function_exists('uv_loop_new')) { 36 + throw new \BadMethodCallException('Cannot create LibUvLoop, ext-uv extension missing'); 37 + } 38 + 39 + $this->uv = \uv_loop_new(); 40 + $this->futureTickQueue = new FutureTickQueue(); 41 + $this->timers = new SplObjectStorage(); 42 + $this->streamListener = $this->createStreamListener(); 43 + $this->signals = new SignalsHandler(); 44 + } 45 + 46 + /** 47 + * Returns the underlying ext-uv event loop. (Internal ReactPHP use only.) 48 + * 49 + * @internal 50 + * 51 + * @return resource 52 + */ 53 + public function getUvLoop() 54 + { 55 + return $this->uv; 56 + } 57 + 58 + /** 59 + * {@inheritdoc} 60 + */ 61 + public function addReadStream($stream, $listener) 62 + { 63 + if (isset($this->readStreams[(int) $stream])) { 64 + return; 65 + } 66 + 67 + $this->readStreams[(int) $stream] = $listener; 68 + $this->addStream($stream); 69 + } 70 + 71 + /** 72 + * {@inheritdoc} 73 + */ 74 + public function addWriteStream($stream, $listener) 75 + { 76 + if (isset($this->writeStreams[(int) $stream])) { 77 + return; 78 + } 79 + 80 + $this->writeStreams[(int) $stream] = $listener; 81 + $this->addStream($stream); 82 + } 83 + 84 + /** 85 + * {@inheritdoc} 86 + */ 87 + public function removeReadStream($stream) 88 + { 89 + if (!isset($this->streamEvents[(int) $stream])) { 90 + return; 91 + } 92 + 93 + unset($this->readStreams[(int) $stream]); 94 + $this->removeStream($stream); 95 + } 96 + 97 + /** 98 + * {@inheritdoc} 99 + */ 100 + public function removeWriteStream($stream) 101 + { 102 + if (!isset($this->streamEvents[(int) $stream])) { 103 + return; 104 + } 105 + 106 + unset($this->writeStreams[(int) $stream]); 107 + $this->removeStream($stream); 108 + } 109 + 110 + /** 111 + * {@inheritdoc} 112 + */ 113 + public function addTimer($interval, $callback) 114 + { 115 + $timer = new Timer($interval, $callback, false); 116 + 117 + $that = $this; 118 + $timers = $this->timers; 119 + $callback = function () use ($timer, $timers, $that) { 120 + \call_user_func($timer->getCallback(), $timer); 121 + 122 + if ($timers->contains($timer)) { 123 + $that->cancelTimer($timer); 124 + } 125 + }; 126 + 127 + $event = \uv_timer_init($this->uv); 128 + $this->timers->attach($timer, $event); 129 + \uv_timer_start( 130 + $event, 131 + $this->convertFloatSecondsToMilliseconds($interval), 132 + 0, 133 + $callback 134 + ); 135 + 136 + return $timer; 137 + } 138 + 139 + /** 140 + * {@inheritdoc} 141 + */ 142 + public function addPeriodicTimer($interval, $callback) 143 + { 144 + $timer = new Timer($interval, $callback, true); 145 + 146 + $callback = function () use ($timer) { 147 + \call_user_func($timer->getCallback(), $timer); 148 + }; 149 + 150 + $interval = $this->convertFloatSecondsToMilliseconds($interval); 151 + $event = \uv_timer_init($this->uv); 152 + $this->timers->attach($timer, $event); 153 + \uv_timer_start( 154 + $event, 155 + $interval, 156 + (int) $interval === 0 ? 1 : $interval, 157 + $callback 158 + ); 159 + 160 + return $timer; 161 + } 162 + 163 + /** 164 + * {@inheritdoc} 165 + */ 166 + public function cancelTimer(TimerInterface $timer) 167 + { 168 + if (isset($this->timers[$timer])) { 169 + @\uv_timer_stop($this->timers[$timer]); 170 + $this->timers->detach($timer); 171 + } 172 + } 173 + 174 + /** 175 + * {@inheritdoc} 176 + */ 177 + public function futureTick($listener) 178 + { 179 + $this->futureTickQueue->add($listener); 180 + } 181 + 182 + public function addSignal($signal, $listener) 183 + { 184 + $this->signals->add($signal, $listener); 185 + 186 + if (!isset($this->signalEvents[$signal])) { 187 + $signals = $this->signals; 188 + $this->signalEvents[$signal] = \uv_signal_init($this->uv); 189 + \uv_signal_start($this->signalEvents[$signal], function () use ($signals, $signal) { 190 + $signals->call($signal); 191 + }, $signal); 192 + } 193 + } 194 + 195 + public function removeSignal($signal, $listener) 196 + { 197 + $this->signals->remove($signal, $listener); 198 + 199 + if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) { 200 + \uv_signal_stop($this->signalEvents[$signal]); 201 + unset($this->signalEvents[$signal]); 202 + } 203 + } 204 + 205 + /** 206 + * {@inheritdoc} 207 + */ 208 + public function run() 209 + { 210 + $this->running = true; 211 + 212 + while ($this->running) { 213 + $this->futureTickQueue->tick(); 214 + 215 + $hasPendingCallbacks = !$this->futureTickQueue->isEmpty(); 216 + $wasJustStopped = !$this->running; 217 + $nothingLeftToDo = !$this->readStreams 218 + && !$this->writeStreams 219 + && !$this->timers->count() 220 + && $this->signals->isEmpty(); 221 + 222 + // Use UV::RUN_ONCE when there are only I/O events active in the loop and block until one of those triggers, 223 + // otherwise use UV::RUN_NOWAIT. 224 + // @link http://docs.libuv.org/en/v1.x/loop.html#c.uv_run 225 + $flags = \UV::RUN_ONCE; 226 + if ($wasJustStopped || $hasPendingCallbacks) { 227 + $flags = \UV::RUN_NOWAIT; 228 + } elseif ($nothingLeftToDo) { 229 + break; 230 + } 231 + 232 + \uv_run($this->uv, $flags); 233 + } 234 + } 235 + 236 + /** 237 + * {@inheritdoc} 238 + */ 239 + public function stop() 240 + { 241 + $this->running = false; 242 + } 243 + 244 + private function addStream($stream) 245 + { 246 + if (!isset($this->streamEvents[(int) $stream])) { 247 + $this->streamEvents[(int)$stream] = \uv_poll_init_socket($this->uv, $stream); 248 + } 249 + 250 + if ($this->streamEvents[(int) $stream] !== false) { 251 + $this->pollStream($stream); 252 + } 253 + } 254 + 255 + private function removeStream($stream) 256 + { 257 + if (!isset($this->streamEvents[(int) $stream])) { 258 + return; 259 + } 260 + 261 + if (!isset($this->readStreams[(int) $stream]) 262 + && !isset($this->writeStreams[(int) $stream])) { 263 + \uv_poll_stop($this->streamEvents[(int) $stream]); 264 + \uv_close($this->streamEvents[(int) $stream]); 265 + unset($this->streamEvents[(int) $stream]); 266 + return; 267 + } 268 + 269 + $this->pollStream($stream); 270 + } 271 + 272 + private function pollStream($stream) 273 + { 274 + if (!isset($this->streamEvents[(int) $stream])) { 275 + return; 276 + } 277 + 278 + $flags = 0; 279 + if (isset($this->readStreams[(int) $stream])) { 280 + $flags |= \UV::READABLE; 281 + } 282 + 283 + if (isset($this->writeStreams[(int) $stream])) { 284 + $flags |= \UV::WRITABLE; 285 + } 286 + 287 + \uv_poll_start($this->streamEvents[(int) $stream], $flags, $this->streamListener); 288 + } 289 + 290 + /** 291 + * Create a stream listener 292 + * 293 + * @return callable Returns a callback 294 + */ 295 + private function createStreamListener() 296 + { 297 + $callback = function ($event, $status, $events, $stream) { 298 + // libuv automatically stops polling on error, re-enable polling to match other loop implementations 299 + if ($status !== 0) { 300 + $this->pollStream($stream); 301 + 302 + // libuv may report no events on error, but this should still invoke stream listeners to report closed connections 303 + // re-enable both readable and writable, correct listeners will be checked below anyway 304 + if ($events === 0) { 305 + $events = \UV::READABLE | \UV::WRITABLE; 306 + } 307 + } 308 + 309 + if (isset($this->readStreams[(int) $stream]) && ($events & \UV::READABLE)) { 310 + \call_user_func($this->readStreams[(int) $stream], $stream); 311 + } 312 + 313 + if (isset($this->writeStreams[(int) $stream]) && ($events & \UV::WRITABLE)) { 314 + \call_user_func($this->writeStreams[(int) $stream], $stream); 315 + } 316 + }; 317 + 318 + return $callback; 319 + } 320 + 321 + /** 322 + * @param float $interval 323 + * @return int 324 + */ 325 + private function convertFloatSecondsToMilliseconds($interval) 326 + { 327 + if ($interval < 0) { 328 + return 0; 329 + } 330 + 331 + $maxValue = (int) (\PHP_INT_MAX / 1000); 332 + $intInterval = (int) $interval; 333 + 334 + if (($intInterval <= 0 && $interval > 1) || $intInterval >= $maxValue) { 335 + throw new \InvalidArgumentException( 336 + "Interval overflow, value must be lower than '{$maxValue}', but '{$interval}' passed." 337 + ); 338 + } 339 + 340 + return (int) \floor($interval * 1000); 341 + } 342 + }
+75
vendor/react/event-loop/src/Factory.php
··· 1 + <?php 2 + 3 + namespace React\EventLoop; 4 + 5 + /** 6 + * [Deprecated] The `Factory` class exists as a convenient way to pick the best available event loop implementation. 7 + * 8 + * @deprecated 1.2.0 See Loop instead. 9 + * @see Loop 10 + */ 11 + final class Factory 12 + { 13 + /** 14 + * [Deprecated] Creates a new event loop instance 15 + * 16 + * ```php 17 + * // deprecated 18 + * $loop = React\EventLoop\Factory::create(); 19 + * 20 + * // new 21 + * $loop = React\EventLoop\Loop::get(); 22 + * ``` 23 + * 24 + * This method always returns an instance implementing `LoopInterface`, 25 + * the actual event loop implementation is an implementation detail. 26 + * 27 + * This method should usually only be called once at the beginning of the program. 28 + * 29 + * @deprecated 1.2.0 See Loop::get() instead. 30 + * @see Loop::get() 31 + * 32 + * @return LoopInterface 33 + */ 34 + public static function create() 35 + { 36 + $loop = self::construct(); 37 + 38 + Loop::set($loop); 39 + 40 + return $loop; 41 + } 42 + 43 + /** 44 + * @internal 45 + * @return LoopInterface 46 + */ 47 + private static function construct() 48 + { 49 + // @codeCoverageIgnoreStart 50 + if (\function_exists('uv_loop_new')) { 51 + // only use ext-uv on PHP 7 52 + return new ExtUvLoop(); 53 + } 54 + 55 + if (\class_exists('libev\EventLoop', false)) { 56 + return new ExtLibevLoop(); 57 + } 58 + 59 + if (\class_exists('EvLoop', false)) { 60 + return new ExtEvLoop(); 61 + } 62 + 63 + if (\class_exists('EventBase', false)) { 64 + return new ExtEventLoop(); 65 + } 66 + 67 + if (\function_exists('event_base_new') && \PHP_MAJOR_VERSION === 5) { 68 + // only use ext-libevent on PHP 5 for now 69 + return new ExtLibeventLoop(); 70 + } 71 + 72 + return new StreamSelectLoop(); 73 + // @codeCoverageIgnoreEnd 74 + } 75 + }
+266
vendor/react/event-loop/src/Loop.php
··· 1 + <?php 2 + 3 + namespace React\EventLoop; 4 + 5 + /** 6 + * The `Loop` class exists as a convenient way to get the currently relevant loop 7 + */ 8 + final class Loop 9 + { 10 + /** 11 + * @var ?LoopInterface 12 + */ 13 + private static $instance; 14 + 15 + /** @var bool */ 16 + private static $stopped = false; 17 + 18 + /** 19 + * Returns the event loop. 20 + * When no loop is set, it will call the factory to create one. 21 + * 22 + * This method always returns an instance implementing `LoopInterface`, 23 + * the actual event loop implementation is an implementation detail. 24 + * 25 + * This method is the preferred way to get the event loop and using 26 + * Factory::create has been deprecated. 27 + * 28 + * @return LoopInterface 29 + */ 30 + public static function get() 31 + { 32 + if (self::$instance instanceof LoopInterface) { 33 + return self::$instance; 34 + } 35 + 36 + self::$instance = $loop = Factory::create(); 37 + 38 + // Automatically run loop at end of program, unless already started or stopped explicitly. 39 + // This is tested using child processes, so coverage is actually 100%, see BinTest. 40 + // @codeCoverageIgnoreStart 41 + $hasRun = false; 42 + $loop->futureTick(function () use (&$hasRun) { 43 + $hasRun = true; 44 + }); 45 + 46 + $stopped =& self::$stopped; 47 + register_shutdown_function(function () use ($loop, &$hasRun, &$stopped) { 48 + // Don't run if we're coming from a fatal error (uncaught exception). 49 + $error = error_get_last(); 50 + if ((isset($error['type']) ? $error['type'] : 0) & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR)) { 51 + return; 52 + } 53 + 54 + if (!$hasRun && !$stopped) { 55 + $loop->run(); 56 + } 57 + }); 58 + // @codeCoverageIgnoreEnd 59 + 60 + return self::$instance; 61 + } 62 + 63 + /** 64 + * Internal undocumented method, behavior might change or throw in the 65 + * future. Use with caution and at your own risk. 66 + * 67 + * @internal 68 + * @return void 69 + */ 70 + public static function set(LoopInterface $loop) 71 + { 72 + self::$instance = $loop; 73 + } 74 + 75 + /** 76 + * [Advanced] Register a listener to be notified when a stream is ready to read. 77 + * 78 + * @param resource $stream 79 + * @param callable $listener 80 + * @return void 81 + * @throws \Exception 82 + * @see LoopInterface::addReadStream() 83 + */ 84 + public static function addReadStream($stream, $listener) 85 + { 86 + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) 87 + if (self::$instance === null) { 88 + self::get(); 89 + } 90 + self::$instance->addReadStream($stream, $listener); 91 + } 92 + 93 + /** 94 + * [Advanced] Register a listener to be notified when a stream is ready to write. 95 + * 96 + * @param resource $stream 97 + * @param callable $listener 98 + * @return void 99 + * @throws \Exception 100 + * @see LoopInterface::addWriteStream() 101 + */ 102 + public static function addWriteStream($stream, $listener) 103 + { 104 + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) 105 + if (self::$instance === null) { 106 + self::get(); 107 + } 108 + self::$instance->addWriteStream($stream, $listener); 109 + } 110 + 111 + /** 112 + * Remove the read event listener for the given stream. 113 + * 114 + * @param resource $stream 115 + * @return void 116 + * @see LoopInterface::removeReadStream() 117 + */ 118 + public static function removeReadStream($stream) 119 + { 120 + if (self::$instance !== null) { 121 + self::$instance->removeReadStream($stream); 122 + } 123 + } 124 + 125 + /** 126 + * Remove the write event listener for the given stream. 127 + * 128 + * @param resource $stream 129 + * @return void 130 + * @see LoopInterface::removeWriteStream() 131 + */ 132 + public static function removeWriteStream($stream) 133 + { 134 + if (self::$instance !== null) { 135 + self::$instance->removeWriteStream($stream); 136 + } 137 + } 138 + 139 + /** 140 + * Enqueue a callback to be invoked once after the given interval. 141 + * 142 + * @param float $interval 143 + * @param callable $callback 144 + * @return TimerInterface 145 + * @see LoopInterface::addTimer() 146 + */ 147 + public static function addTimer($interval, $callback) 148 + { 149 + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) 150 + if (self::$instance === null) { 151 + self::get(); 152 + } 153 + return self::$instance->addTimer($interval, $callback); 154 + } 155 + 156 + /** 157 + * Enqueue a callback to be invoked repeatedly after the given interval. 158 + * 159 + * @param float $interval 160 + * @param callable $callback 161 + * @return TimerInterface 162 + * @see LoopInterface::addPeriodicTimer() 163 + */ 164 + public static function addPeriodicTimer($interval, $callback) 165 + { 166 + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) 167 + if (self::$instance === null) { 168 + self::get(); 169 + } 170 + return self::$instance->addPeriodicTimer($interval, $callback); 171 + } 172 + 173 + /** 174 + * Cancel a pending timer. 175 + * 176 + * @param TimerInterface $timer 177 + * @return void 178 + * @see LoopInterface::cancelTimer() 179 + */ 180 + public static function cancelTimer(TimerInterface $timer) 181 + { 182 + if (self::$instance !== null) { 183 + self::$instance->cancelTimer($timer); 184 + } 185 + } 186 + 187 + /** 188 + * Schedule a callback to be invoked on a future tick of the event loop. 189 + * 190 + * @param callable $listener 191 + * @return void 192 + * @see LoopInterface::futureTick() 193 + */ 194 + public static function futureTick($listener) 195 + { 196 + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) 197 + if (self::$instance === null) { 198 + self::get(); 199 + } 200 + 201 + self::$instance->futureTick($listener); 202 + } 203 + 204 + /** 205 + * Register a listener to be notified when a signal has been caught by this process. 206 + * 207 + * @param int $signal 208 + * @param callable $listener 209 + * @return void 210 + * @see LoopInterface::addSignal() 211 + */ 212 + public static function addSignal($signal, $listener) 213 + { 214 + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) 215 + if (self::$instance === null) { 216 + self::get(); 217 + } 218 + 219 + self::$instance->addSignal($signal, $listener); 220 + } 221 + 222 + /** 223 + * Removes a previously added signal listener. 224 + * 225 + * @param int $signal 226 + * @param callable $listener 227 + * @return void 228 + * @see LoopInterface::removeSignal() 229 + */ 230 + public static function removeSignal($signal, $listener) 231 + { 232 + if (self::$instance !== null) { 233 + self::$instance->removeSignal($signal, $listener); 234 + } 235 + } 236 + 237 + /** 238 + * Run the event loop until there are no more tasks to perform. 239 + * 240 + * @return void 241 + * @see LoopInterface::run() 242 + */ 243 + public static function run() 244 + { 245 + // create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls) 246 + if (self::$instance === null) { 247 + self::get(); 248 + } 249 + 250 + self::$instance->run(); 251 + } 252 + 253 + /** 254 + * Instruct a running event loop to stop. 255 + * 256 + * @return void 257 + * @see LoopInterface::stop() 258 + */ 259 + public static function stop() 260 + { 261 + self::$stopped = true; 262 + if (self::$instance !== null) { 263 + self::$instance->stop(); 264 + } 265 + } 266 + }
+472
vendor/react/event-loop/src/LoopInterface.php
··· 1 + <?php 2 + 3 + namespace React\EventLoop; 4 + 5 + interface LoopInterface 6 + { 7 + /** 8 + * [Advanced] Register a listener to be notified when a stream is ready to read. 9 + * 10 + * Note that this low-level API is considered advanced usage. 11 + * Most use cases should probably use the higher-level 12 + * [readable Stream API](https://github.com/reactphp/stream#readablestreaminterface) 13 + * instead. 14 + * 15 + * The first parameter MUST be a valid stream resource that supports 16 + * checking whether it is ready to read by this loop implementation. 17 + * A single stream resource MUST NOT be added more than once. 18 + * Instead, either call [`removeReadStream()`](#removereadstream) first or 19 + * react to this event with a single listener and then dispatch from this 20 + * listener. This method MAY throw an `Exception` if the given resource type 21 + * is not supported by this loop implementation. 22 + * 23 + * The second parameter MUST be a listener callback function that accepts 24 + * the stream resource as its only parameter. 25 + * If you don't use the stream resource inside your listener callback function 26 + * you MAY use a function which has no parameters at all. 27 + * 28 + * The listener callback function MUST NOT throw an `Exception`. 29 + * The return value of the listener callback function will be ignored and has 30 + * no effect, so for performance reasons you're recommended to not return 31 + * any excessive data structures. 32 + * 33 + * If you want to access any variables within your callback function, you 34 + * can bind arbitrary data to a callback closure like this: 35 + * 36 + * ```php 37 + * $loop->addReadStream($stream, function ($stream) use ($name) { 38 + * echo $name . ' said: ' . fread($stream); 39 + * }); 40 + * ``` 41 + * 42 + * See also [example #11](examples). 43 + * 44 + * You can invoke [`removeReadStream()`](#removereadstream) to remove the 45 + * read event listener for this stream. 46 + * 47 + * The execution order of listeners when multiple streams become ready at 48 + * the same time is not guaranteed. 49 + * 50 + * @param resource $stream The PHP stream resource to check. 51 + * @param callable $listener Invoked when the stream is ready. 52 + * @throws \Exception if the given resource type is not supported by this loop implementation 53 + * @see self::removeReadStream() 54 + */ 55 + public function addReadStream($stream, $listener); 56 + 57 + /** 58 + * [Advanced] Register a listener to be notified when a stream is ready to write. 59 + * 60 + * Note that this low-level API is considered advanced usage. 61 + * Most use cases should probably use the higher-level 62 + * [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface) 63 + * instead. 64 + * 65 + * The first parameter MUST be a valid stream resource that supports 66 + * checking whether it is ready to write by this loop implementation. 67 + * A single stream resource MUST NOT be added more than once. 68 + * Instead, either call [`removeWriteStream()`](#removewritestream) first or 69 + * react to this event with a single listener and then dispatch from this 70 + * listener. This method MAY throw an `Exception` if the given resource type 71 + * is not supported by this loop implementation. 72 + * 73 + * The second parameter MUST be a listener callback function that accepts 74 + * the stream resource as its only parameter. 75 + * If you don't use the stream resource inside your listener callback function 76 + * you MAY use a function which has no parameters at all. 77 + * 78 + * The listener callback function MUST NOT throw an `Exception`. 79 + * The return value of the listener callback function will be ignored and has 80 + * no effect, so for performance reasons you're recommended to not return 81 + * any excessive data structures. 82 + * 83 + * If you want to access any variables within your callback function, you 84 + * can bind arbitrary data to a callback closure like this: 85 + * 86 + * ```php 87 + * $loop->addWriteStream($stream, function ($stream) use ($name) { 88 + * fwrite($stream, 'Hello ' . $name); 89 + * }); 90 + * ``` 91 + * 92 + * See also [example #12](examples). 93 + * 94 + * You can invoke [`removeWriteStream()`](#removewritestream) to remove the 95 + * write event listener for this stream. 96 + * 97 + * The execution order of listeners when multiple streams become ready at 98 + * the same time is not guaranteed. 99 + * 100 + * Some event loop implementations are known to only trigger the listener if 101 + * the stream *becomes* readable (edge-triggered) and may not trigger if the 102 + * stream has already been readable from the beginning. 103 + * This also implies that a stream may not be recognized as readable when data 104 + * is still left in PHP's internal stream buffers. 105 + * As such, it's recommended to use `stream_set_read_buffer($stream, 0);` 106 + * to disable PHP's internal read buffer in this case. 107 + * 108 + * @param resource $stream The PHP stream resource to check. 109 + * @param callable $listener Invoked when the stream is ready. 110 + * @throws \Exception if the given resource type is not supported by this loop implementation 111 + * @see self::removeWriteStream() 112 + */ 113 + public function addWriteStream($stream, $listener); 114 + 115 + /** 116 + * Remove the read event listener for the given stream. 117 + * 118 + * Removing a stream from the loop that has already been removed or trying 119 + * to remove a stream that was never added or is invalid has no effect. 120 + * 121 + * @param resource $stream The PHP stream resource. 122 + */ 123 + public function removeReadStream($stream); 124 + 125 + /** 126 + * Remove the write event listener for the given stream. 127 + * 128 + * Removing a stream from the loop that has already been removed or trying 129 + * to remove a stream that was never added or is invalid has no effect. 130 + * 131 + * @param resource $stream The PHP stream resource. 132 + */ 133 + public function removeWriteStream($stream); 134 + 135 + /** 136 + * Enqueue a callback to be invoked once after the given interval. 137 + * 138 + * The second parameter MUST be a timer callback function that accepts 139 + * the timer instance as its only parameter. 140 + * If you don't use the timer instance inside your timer callback function 141 + * you MAY use a function which has no parameters at all. 142 + * 143 + * The timer callback function MUST NOT throw an `Exception`. 144 + * The return value of the timer callback function will be ignored and has 145 + * no effect, so for performance reasons you're recommended to not return 146 + * any excessive data structures. 147 + * 148 + * This method returns a timer instance. The same timer instance will also be 149 + * passed into the timer callback function as described above. 150 + * You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer. 151 + * Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure 152 + * the callback will be invoked only once after the given interval. 153 + * 154 + * ```php 155 + * $loop->addTimer(0.8, function () { 156 + * echo 'world!' . PHP_EOL; 157 + * }); 158 + * 159 + * $loop->addTimer(0.3, function () { 160 + * echo 'hello '; 161 + * }); 162 + * ``` 163 + * 164 + * See also [example #1](examples). 165 + * 166 + * If you want to access any variables within your callback function, you 167 + * can bind arbitrary data to a callback closure like this: 168 + * 169 + * ```php 170 + * function hello($name, LoopInterface $loop) 171 + * { 172 + * $loop->addTimer(1.0, function () use ($name) { 173 + * echo "hello $name\n"; 174 + * }); 175 + * } 176 + * 177 + * hello('Tester', $loop); 178 + * ``` 179 + * 180 + * This interface does not enforce any particular timer resolution, so 181 + * special care may have to be taken if you rely on very high precision with 182 + * millisecond accuracy or below. Event loop implementations SHOULD work on 183 + * a best effort basis and SHOULD provide at least millisecond accuracy 184 + * unless otherwise noted. Many existing event loop implementations are 185 + * known to provide microsecond accuracy, but it's generally not recommended 186 + * to rely on this high precision. 187 + * 188 + * Similarly, the execution order of timers scheduled to execute at the 189 + * same time (within its possible accuracy) is not guaranteed. 190 + * 191 + * This interface suggests that event loop implementations SHOULD use a 192 + * monotonic time source if available. Given that a monotonic time source is 193 + * only available as of PHP 7.3 by default, event loop implementations MAY 194 + * fall back to using wall-clock time. 195 + * While this does not affect many common use cases, this is an important 196 + * distinction for programs that rely on a high time precision or on systems 197 + * that are subject to discontinuous time adjustments (time jumps). 198 + * This means that if you schedule a timer to trigger in 30s and then adjust 199 + * your system time forward by 20s, the timer SHOULD still trigger in 30s. 200 + * See also [event loop implementations](#loop-implementations) for more details. 201 + * 202 + * @param int|float $interval The number of seconds to wait before execution. 203 + * @param callable $callback The callback to invoke. 204 + * 205 + * @return TimerInterface 206 + */ 207 + public function addTimer($interval, $callback); 208 + 209 + /** 210 + * Enqueue a callback to be invoked repeatedly after the given interval. 211 + * 212 + * The second parameter MUST be a timer callback function that accepts 213 + * the timer instance as its only parameter. 214 + * If you don't use the timer instance inside your timer callback function 215 + * you MAY use a function which has no parameters at all. 216 + * 217 + * The timer callback function MUST NOT throw an `Exception`. 218 + * The return value of the timer callback function will be ignored and has 219 + * no effect, so for performance reasons you're recommended to not return 220 + * any excessive data structures. 221 + * 222 + * This method returns a timer instance. The same timer instance will also be 223 + * passed into the timer callback function as described above. 224 + * Unlike [`addTimer()`](#addtimer), this method will ensure the callback 225 + * will be invoked infinitely after the given interval or until you invoke 226 + * [`cancelTimer`](#canceltimer). 227 + * 228 + * ```php 229 + * $timer = $loop->addPeriodicTimer(0.1, function () { 230 + * echo 'tick!' . PHP_EOL; 231 + * }); 232 + * 233 + * $loop->addTimer(1.0, function () use ($loop, $timer) { 234 + * $loop->cancelTimer($timer); 235 + * echo 'Done' . PHP_EOL; 236 + * }); 237 + * ``` 238 + * 239 + * See also [example #2](examples). 240 + * 241 + * If you want to limit the number of executions, you can bind 242 + * arbitrary data to a callback closure like this: 243 + * 244 + * ```php 245 + * function hello($name, LoopInterface $loop) 246 + * { 247 + * $n = 3; 248 + * $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) { 249 + * if ($n > 0) { 250 + * --$n; 251 + * echo "hello $name\n"; 252 + * } else { 253 + * $loop->cancelTimer($timer); 254 + * } 255 + * }); 256 + * } 257 + * 258 + * hello('Tester', $loop); 259 + * ``` 260 + * 261 + * This interface does not enforce any particular timer resolution, so 262 + * special care may have to be taken if you rely on very high precision with 263 + * millisecond accuracy or below. Event loop implementations SHOULD work on 264 + * a best effort basis and SHOULD provide at least millisecond accuracy 265 + * unless otherwise noted. Many existing event loop implementations are 266 + * known to provide microsecond accuracy, but it's generally not recommended 267 + * to rely on this high precision. 268 + * 269 + * Similarly, the execution order of timers scheduled to execute at the 270 + * same time (within its possible accuracy) is not guaranteed. 271 + * 272 + * This interface suggests that event loop implementations SHOULD use a 273 + * monotonic time source if available. Given that a monotonic time source is 274 + * only available as of PHP 7.3 by default, event loop implementations MAY 275 + * fall back to using wall-clock time. 276 + * While this does not affect many common use cases, this is an important 277 + * distinction for programs that rely on a high time precision or on systems 278 + * that are subject to discontinuous time adjustments (time jumps). 279 + * This means that if you schedule a timer to trigger in 30s and then adjust 280 + * your system time forward by 20s, the timer SHOULD still trigger in 30s. 281 + * See also [event loop implementations](#loop-implementations) for more details. 282 + * 283 + * Additionally, periodic timers may be subject to timer drift due to 284 + * re-scheduling after each invocation. As such, it's generally not 285 + * recommended to rely on this for high precision intervals with millisecond 286 + * accuracy or below. 287 + * 288 + * @param int|float $interval The number of seconds to wait before execution. 289 + * @param callable $callback The callback to invoke. 290 + * 291 + * @return TimerInterface 292 + */ 293 + public function addPeriodicTimer($interval, $callback); 294 + 295 + /** 296 + * Cancel a pending timer. 297 + * 298 + * See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples). 299 + * 300 + * Calling this method on a timer instance that has not been added to this 301 + * loop instance or on a timer that has already been cancelled has no effect. 302 + * 303 + * @param TimerInterface $timer The timer to cancel. 304 + * 305 + * @return void 306 + */ 307 + public function cancelTimer(TimerInterface $timer); 308 + 309 + /** 310 + * Schedule a callback to be invoked on a future tick of the event loop. 311 + * 312 + * This works very much similar to timers with an interval of zero seconds, 313 + * but does not require the overhead of scheduling a timer queue. 314 + * 315 + * The tick callback function MUST be able to accept zero parameters. 316 + * 317 + * The tick callback function MUST NOT throw an `Exception`. 318 + * The return value of the tick callback function will be ignored and has 319 + * no effect, so for performance reasons you're recommended to not return 320 + * any excessive data structures. 321 + * 322 + * If you want to access any variables within your callback function, you 323 + * can bind arbitrary data to a callback closure like this: 324 + * 325 + * ```php 326 + * function hello($name, LoopInterface $loop) 327 + * { 328 + * $loop->futureTick(function () use ($name) { 329 + * echo "hello $name\n"; 330 + * }); 331 + * } 332 + * 333 + * hello('Tester', $loop); 334 + * ``` 335 + * 336 + * Unlike timers, tick callbacks are guaranteed to be executed in the order 337 + * they are enqueued. 338 + * Also, once a callback is enqueued, there's no way to cancel this operation. 339 + * 340 + * This is often used to break down bigger tasks into smaller steps (a form 341 + * of cooperative multitasking). 342 + * 343 + * ```php 344 + * $loop->futureTick(function () { 345 + * echo 'b'; 346 + * }); 347 + * $loop->futureTick(function () { 348 + * echo 'c'; 349 + * }); 350 + * echo 'a'; 351 + * ``` 352 + * 353 + * See also [example #3](examples). 354 + * 355 + * @param callable $listener The callback to invoke. 356 + * 357 + * @return void 358 + */ 359 + public function futureTick($listener); 360 + 361 + /** 362 + * Register a listener to be notified when a signal has been caught by this process. 363 + * 364 + * This is useful to catch user interrupt signals or shutdown signals from 365 + * tools like `supervisor` or `systemd`. 366 + * 367 + * The second parameter MUST be a listener callback function that accepts 368 + * the signal as its only parameter. 369 + * If you don't use the signal inside your listener callback function 370 + * you MAY use a function which has no parameters at all. 371 + * 372 + * The listener callback function MUST NOT throw an `Exception`. 373 + * The return value of the listener callback function will be ignored and has 374 + * no effect, so for performance reasons you're recommended to not return 375 + * any excessive data structures. 376 + * 377 + * ```php 378 + * $loop->addSignal(SIGINT, function (int $signal) { 379 + * echo 'Caught user interrupt signal' . PHP_EOL; 380 + * }); 381 + * ``` 382 + * 383 + * See also [example #4](examples). 384 + * 385 + * Signaling is only available on Unix-like platforms, Windows isn't 386 + * supported due to operating system limitations. 387 + * This method may throw a `BadMethodCallException` if signals aren't 388 + * supported on this platform, for example when required extensions are 389 + * missing. 390 + * 391 + * **Note: A listener can only be added once to the same signal, any 392 + * attempts to add it more than once will be ignored.** 393 + * 394 + * @param int $signal 395 + * @param callable $listener 396 + * 397 + * @throws \BadMethodCallException when signals aren't supported on this 398 + * platform, for example when required extensions are missing. 399 + * 400 + * @return void 401 + */ 402 + public function addSignal($signal, $listener); 403 + 404 + /** 405 + * Removes a previously added signal listener. 406 + * 407 + * ```php 408 + * $loop->removeSignal(SIGINT, $listener); 409 + * ``` 410 + * 411 + * Any attempts to remove listeners that aren't registered will be ignored. 412 + * 413 + * @param int $signal 414 + * @param callable $listener 415 + * 416 + * @return void 417 + */ 418 + public function removeSignal($signal, $listener); 419 + 420 + /** 421 + * Run the event loop until there are no more tasks to perform. 422 + * 423 + * For many applications, this method is the only directly visible 424 + * invocation on the event loop. 425 + * As a rule of thumb, it is usually recommended to attach everything to the 426 + * same loop instance and then run the loop once at the bottom end of the 427 + * application. 428 + * 429 + * ```php 430 + * $loop->run(); 431 + * ``` 432 + * 433 + * This method will keep the loop running until there are no more tasks 434 + * to perform. In other words: This method will block until the last 435 + * timer, stream and/or signal has been removed. 436 + * 437 + * Likewise, it is imperative to ensure the application actually invokes 438 + * this method once. Adding listeners to the loop and missing to actually 439 + * run it will result in the application exiting without actually waiting 440 + * for any of the attached listeners. 441 + * 442 + * This method MUST NOT be called while the loop is already running. 443 + * This method MAY be called more than once after it has explicitly been 444 + * [`stop()`ped](#stop) or after it automatically stopped because it 445 + * previously did no longer have anything to do. 446 + * 447 + * @return void 448 + */ 449 + public function run(); 450 + 451 + /** 452 + * Instruct a running event loop to stop. 453 + * 454 + * This method is considered advanced usage and should be used with care. 455 + * As a rule of thumb, it is usually recommended to let the loop stop 456 + * only automatically when it no longer has anything to do. 457 + * 458 + * This method can be used to explicitly instruct the event loop to stop: 459 + * 460 + * ```php 461 + * $loop->addTimer(3.0, function () use ($loop) { 462 + * $loop->stop(); 463 + * }); 464 + * ``` 465 + * 466 + * Calling this method on a loop instance that is not currently running or 467 + * on a loop instance that has already been stopped has no effect. 468 + * 469 + * @return void 470 + */ 471 + public function stop(); 472 + }
+63
vendor/react/event-loop/src/SignalsHandler.php
··· 1 + <?php 2 + 3 + namespace React\EventLoop; 4 + 5 + /** 6 + * @internal 7 + */ 8 + final class SignalsHandler 9 + { 10 + private $signals = array(); 11 + 12 + public function add($signal, $listener) 13 + { 14 + if (!isset($this->signals[$signal])) { 15 + $this->signals[$signal] = array(); 16 + } 17 + 18 + if (\in_array($listener, $this->signals[$signal])) { 19 + return; 20 + } 21 + 22 + $this->signals[$signal][] = $listener; 23 + } 24 + 25 + public function remove($signal, $listener) 26 + { 27 + if (!isset($this->signals[$signal])) { 28 + return; 29 + } 30 + 31 + $index = \array_search($listener, $this->signals[$signal], true); 32 + unset($this->signals[$signal][$index]); 33 + 34 + if (isset($this->signals[$signal]) && \count($this->signals[$signal]) === 0) { 35 + unset($this->signals[$signal]); 36 + } 37 + } 38 + 39 + public function call($signal) 40 + { 41 + if (!isset($this->signals[$signal])) { 42 + return; 43 + } 44 + 45 + foreach ($this->signals[$signal] as $listener) { 46 + \call_user_func($listener, $signal); 47 + } 48 + } 49 + 50 + public function count($signal) 51 + { 52 + if (!isset($this->signals[$signal])) { 53 + return 0; 54 + } 55 + 56 + return \count($this->signals[$signal]); 57 + } 58 + 59 + public function isEmpty() 60 + { 61 + return !$this->signals; 62 + } 63 + }
+330
vendor/react/event-loop/src/StreamSelectLoop.php
··· 1 + <?php 2 + 3 + namespace React\EventLoop; 4 + 5 + use React\EventLoop\Tick\FutureTickQueue; 6 + use React\EventLoop\Timer\Timer; 7 + use React\EventLoop\Timer\Timers; 8 + 9 + /** 10 + * A `stream_select()` based event loop. 11 + * 12 + * This uses the [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php) 13 + * function and is the only implementation that works out of the box with PHP. 14 + * 15 + * This event loop works out of the box on PHP 5.4 through PHP 8+ and HHVM. 16 + * This means that no installation is required and this library works on all 17 + * platforms and supported PHP versions. 18 + * Accordingly, the [`Loop` class](#loop) and the deprecated [`Factory`](#factory) 19 + * will use this event loop by default if you do not install any of the event loop 20 + * extensions listed below. 21 + * 22 + * Under the hood, it does a simple `select` system call. 23 + * This system call is limited to the maximum file descriptor number of 24 + * `FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)` 25 + * (`m` being the maximum file descriptor number passed). 26 + * This means that you may run into issues when handling thousands of streams 27 + * concurrently and you may want to look into using one of the alternative 28 + * event loop implementations listed below in this case. 29 + * If your use case is among the many common use cases that involve handling only 30 + * dozens or a few hundred streams at once, then this event loop implementation 31 + * performs really well. 32 + * 33 + * If you want to use signal handling (see also [`addSignal()`](#addsignal) below), 34 + * this event loop implementation requires `ext-pcntl`. 35 + * This extension is only available for Unix-like platforms and does not support 36 + * Windows. 37 + * It is commonly installed as part of many PHP distributions. 38 + * If this extension is missing (or you're running on Windows), signal handling is 39 + * not supported and throws a `BadMethodCallException` instead. 40 + * 41 + * This event loop is known to rely on wall-clock time to schedule future timers 42 + * when using any version before PHP 7.3, because a monotonic time source is 43 + * only available as of PHP 7.3 (`hrtime()`). 44 + * While this does not affect many common use cases, this is an important 45 + * distinction for programs that rely on a high time precision or on systems 46 + * that are subject to discontinuous time adjustments (time jumps). 47 + * This means that if you schedule a timer to trigger in 30s on PHP < 7.3 and 48 + * then adjust your system time forward by 20s, the timer may trigger in 10s. 49 + * See also [`addTimer()`](#addtimer) for more details. 50 + * 51 + * @link https://www.php.net/manual/en/function.stream-select.php 52 + */ 53 + final class StreamSelectLoop implements LoopInterface 54 + { 55 + /** @internal */ 56 + const MICROSECONDS_PER_SECOND = 1000000; 57 + 58 + private $futureTickQueue; 59 + private $timers; 60 + private $readStreams = array(); 61 + private $readListeners = array(); 62 + private $writeStreams = array(); 63 + private $writeListeners = array(); 64 + private $running; 65 + private $pcntl = false; 66 + private $pcntlPoll = false; 67 + private $signals; 68 + 69 + public function __construct() 70 + { 71 + $this->futureTickQueue = new FutureTickQueue(); 72 + $this->timers = new Timers(); 73 + $this->pcntl = \function_exists('pcntl_signal') && \function_exists('pcntl_signal_dispatch'); 74 + $this->pcntlPoll = $this->pcntl && !\function_exists('pcntl_async_signals'); 75 + $this->signals = new SignalsHandler(); 76 + 77 + // prefer async signals if available (PHP 7.1+) or fall back to dispatching on each tick 78 + if ($this->pcntl && !$this->pcntlPoll) { 79 + \pcntl_async_signals(true); 80 + } 81 + } 82 + 83 + public function addReadStream($stream, $listener) 84 + { 85 + $key = (int) $stream; 86 + 87 + if (!isset($this->readStreams[$key])) { 88 + $this->readStreams[$key] = $stream; 89 + $this->readListeners[$key] = $listener; 90 + } 91 + } 92 + 93 + public function addWriteStream($stream, $listener) 94 + { 95 + $key = (int) $stream; 96 + 97 + if (!isset($this->writeStreams[$key])) { 98 + $this->writeStreams[$key] = $stream; 99 + $this->writeListeners[$key] = $listener; 100 + } 101 + } 102 + 103 + public function removeReadStream($stream) 104 + { 105 + $key = (int) $stream; 106 + 107 + unset( 108 + $this->readStreams[$key], 109 + $this->readListeners[$key] 110 + ); 111 + } 112 + 113 + public function removeWriteStream($stream) 114 + { 115 + $key = (int) $stream; 116 + 117 + unset( 118 + $this->writeStreams[$key], 119 + $this->writeListeners[$key] 120 + ); 121 + } 122 + 123 + public function addTimer($interval, $callback) 124 + { 125 + $timer = new Timer($interval, $callback, false); 126 + 127 + $this->timers->add($timer); 128 + 129 + return $timer; 130 + } 131 + 132 + public function addPeriodicTimer($interval, $callback) 133 + { 134 + $timer = new Timer($interval, $callback, true); 135 + 136 + $this->timers->add($timer); 137 + 138 + return $timer; 139 + } 140 + 141 + public function cancelTimer(TimerInterface $timer) 142 + { 143 + $this->timers->cancel($timer); 144 + } 145 + 146 + public function futureTick($listener) 147 + { 148 + $this->futureTickQueue->add($listener); 149 + } 150 + 151 + public function addSignal($signal, $listener) 152 + { 153 + if ($this->pcntl === false) { 154 + throw new \BadMethodCallException('Event loop feature "signals" isn\'t supported by the "StreamSelectLoop"'); 155 + } 156 + 157 + $first = $this->signals->count($signal) === 0; 158 + $this->signals->add($signal, $listener); 159 + 160 + if ($first) { 161 + \pcntl_signal($signal, array($this->signals, 'call')); 162 + } 163 + } 164 + 165 + public function removeSignal($signal, $listener) 166 + { 167 + if (!$this->signals->count($signal)) { 168 + return; 169 + } 170 + 171 + $this->signals->remove($signal, $listener); 172 + 173 + if ($this->signals->count($signal) === 0) { 174 + \pcntl_signal($signal, \SIG_DFL); 175 + } 176 + } 177 + 178 + public function run() 179 + { 180 + $this->running = true; 181 + 182 + while ($this->running) { 183 + $this->futureTickQueue->tick(); 184 + 185 + $this->timers->tick(); 186 + 187 + // Future-tick queue has pending callbacks ... 188 + if (!$this->running || !$this->futureTickQueue->isEmpty()) { 189 + $timeout = 0; 190 + 191 + // There is a pending timer, only block until it is due ... 192 + } elseif ($scheduledAt = $this->timers->getFirst()) { 193 + $timeout = $scheduledAt - $this->timers->getTime(); 194 + if ($timeout < 0) { 195 + $timeout = 0; 196 + } else { 197 + // Convert float seconds to int microseconds. 198 + // Ensure we do not exceed maximum integer size, which may 199 + // cause the loop to tick once every ~35min on 32bit systems. 200 + $timeout *= self::MICROSECONDS_PER_SECOND; 201 + $timeout = $timeout > \PHP_INT_MAX ? \PHP_INT_MAX : (int)$timeout; 202 + } 203 + 204 + // The only possible event is stream or signal activity, so wait forever ... 205 + } elseif ($this->readStreams || $this->writeStreams || !$this->signals->isEmpty()) { 206 + $timeout = null; 207 + 208 + // There's nothing left to do ... 209 + } else { 210 + break; 211 + } 212 + 213 + $this->waitForStreamActivity($timeout); 214 + } 215 + } 216 + 217 + public function stop() 218 + { 219 + $this->running = false; 220 + } 221 + 222 + /** 223 + * Wait/check for stream activity, or until the next timer is due. 224 + * 225 + * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever. 226 + */ 227 + private function waitForStreamActivity($timeout) 228 + { 229 + $read = $this->readStreams; 230 + $write = $this->writeStreams; 231 + 232 + $available = $this->streamSelect($read, $write, $timeout); 233 + if ($this->pcntlPoll) { 234 + \pcntl_signal_dispatch(); 235 + } 236 + if (false === $available) { 237 + // if a system call has been interrupted, 238 + // we cannot rely on it's outcome 239 + return; 240 + } 241 + 242 + foreach ($read as $stream) { 243 + $key = (int) $stream; 244 + 245 + if (isset($this->readListeners[$key])) { 246 + \call_user_func($this->readListeners[$key], $stream); 247 + } 248 + } 249 + 250 + foreach ($write as $stream) { 251 + $key = (int) $stream; 252 + 253 + if (isset($this->writeListeners[$key])) { 254 + \call_user_func($this->writeListeners[$key], $stream); 255 + } 256 + } 257 + } 258 + 259 + /** 260 + * Emulate a stream_select() implementation that does not break when passed 261 + * empty stream arrays. 262 + * 263 + * @param array $read An array of read streams to select upon. 264 + * @param array $write An array of write streams to select upon. 265 + * @param int|null $timeout Activity timeout in microseconds, or null to wait forever. 266 + * 267 + * @return int|false The total number of streams that are ready for read/write. 268 + * Can return false if stream_select() is interrupted by a signal. 269 + */ 270 + private function streamSelect(array &$read, array &$write, $timeout) 271 + { 272 + if ($read || $write) { 273 + // We do not usually use or expose the `exceptfds` parameter passed to the underlying `select`. 274 + // However, Windows does not report failed connection attempts in `writefds` passed to `select` like most other platforms. 275 + // Instead, it uses `writefds` only for successful connection attempts and `exceptfds` for failed connection attempts. 276 + // 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. 277 + // This ensures the public API matches other loop implementations across all platforms (see also test suite or rather test matrix). 278 + // Lacking better APIs, every write-only socket that has not yet read any data is assumed to be in a pending connection attempt state. 279 + // @link https://docs.microsoft.com/de-de/windows/win32/api/winsock2/nf-winsock2-select 280 + $except = null; 281 + if (\DIRECTORY_SEPARATOR === '\\') { 282 + $except = array(); 283 + foreach ($write as $key => $socket) { 284 + if (!isset($read[$key]) && @\ftell($socket) === 0) { 285 + $except[$key] = $socket; 286 + } 287 + } 288 + } 289 + 290 + /** @var ?callable $previous */ 291 + $previous = \set_error_handler(function ($errno, $errstr) use (&$previous) { 292 + // suppress warnings that occur when `stream_select()` is interrupted by a signal 293 + // PHP defines `EINTR` through `ext-sockets` or `ext-pcntl`, otherwise use common default (Linux & Mac) 294 + $eintr = \defined('SOCKET_EINTR') ? \SOCKET_EINTR : (\defined('PCNTL_EINTR') ? \PCNTL_EINTR : 4); 295 + if ($errno === \E_WARNING && \strpos($errstr, '[' . $eintr .']: ') !== false) { 296 + return; 297 + } 298 + 299 + // forward any other error to registered error handler or print warning 300 + return ($previous !== null) ? \call_user_func_array($previous, \func_get_args()) : false; 301 + }); 302 + 303 + try { 304 + $ret = \stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); 305 + \restore_error_handler(); 306 + } catch (\Throwable $e) { // @codeCoverageIgnoreStart 307 + \restore_error_handler(); 308 + throw $e; 309 + } catch (\Exception $e) { 310 + \restore_error_handler(); 311 + throw $e; 312 + } // @codeCoverageIgnoreEnd 313 + 314 + if ($except) { 315 + $write = \array_merge($write, $except); 316 + } 317 + return $ret; 318 + } 319 + 320 + if ($timeout > 0) { 321 + \usleep($timeout); 322 + } elseif ($timeout === null) { 323 + // wait forever (we only reach this if we're only awaiting signals) 324 + // this may be interrupted and return earlier when a signal is received 325 + \sleep(PHP_INT_MAX); 326 + } 327 + 328 + return 0; 329 + } 330 + }
+60
vendor/react/event-loop/src/Tick/FutureTickQueue.php
··· 1 + <?php 2 + 3 + namespace React\EventLoop\Tick; 4 + 5 + use SplQueue; 6 + 7 + /** 8 + * A tick queue implementation that can hold multiple callback functions 9 + * 10 + * This class should only be used internally, see LoopInterface instead. 11 + * 12 + * @see LoopInterface 13 + * @internal 14 + */ 15 + final class FutureTickQueue 16 + { 17 + private $queue; 18 + 19 + public function __construct() 20 + { 21 + $this->queue = new SplQueue(); 22 + } 23 + 24 + /** 25 + * Add a callback to be invoked on a future tick of the event loop. 26 + * 27 + * Callbacks are guaranteed to be executed in the order they are enqueued. 28 + * 29 + * @param callable $listener The callback to invoke. 30 + */ 31 + public function add($listener) 32 + { 33 + $this->queue->enqueue($listener); 34 + } 35 + 36 + /** 37 + * Flush the callback queue. 38 + */ 39 + public function tick() 40 + { 41 + // Only invoke as many callbacks as were on the queue when tick() was called. 42 + $count = $this->queue->count(); 43 + 44 + while ($count--) { 45 + \call_user_func( 46 + $this->queue->dequeue() 47 + ); 48 + } 49 + } 50 + 51 + /** 52 + * Check if the next tick queue is empty. 53 + * 54 + * @return boolean 55 + */ 56 + public function isEmpty() 57 + { 58 + return $this->queue->isEmpty(); 59 + } 60 + }
+55
vendor/react/event-loop/src/Timer/Timer.php
··· 1 + <?php 2 + 3 + namespace React\EventLoop\Timer; 4 + 5 + use React\EventLoop\TimerInterface; 6 + 7 + /** 8 + * The actual connection implementation for TimerInterface 9 + * 10 + * This class should only be used internally, see TimerInterface instead. 11 + * 12 + * @see TimerInterface 13 + * @internal 14 + */ 15 + final class Timer implements TimerInterface 16 + { 17 + const MIN_INTERVAL = 0.000001; 18 + 19 + private $interval; 20 + private $callback; 21 + private $periodic; 22 + 23 + /** 24 + * Constructor initializes the fields of the Timer 25 + * 26 + * @param float $interval The interval after which this timer will execute, in seconds 27 + * @param callable $callback The callback that will be executed when this timer elapses 28 + * @param bool $periodic Whether the time is periodic 29 + */ 30 + public function __construct($interval, $callback, $periodic = false) 31 + { 32 + if ($interval < self::MIN_INTERVAL) { 33 + $interval = self::MIN_INTERVAL; 34 + } 35 + 36 + $this->interval = (float) $interval; 37 + $this->callback = $callback; 38 + $this->periodic = (bool) $periodic; 39 + } 40 + 41 + public function getInterval() 42 + { 43 + return $this->interval; 44 + } 45 + 46 + public function getCallback() 47 + { 48 + return $this->callback; 49 + } 50 + 51 + public function isPeriodic() 52 + { 53 + return $this->periodic; 54 + } 55 + }
+113
vendor/react/event-loop/src/Timer/Timers.php
··· 1 + <?php 2 + 3 + namespace React\EventLoop\Timer; 4 + 5 + use React\EventLoop\TimerInterface; 6 + 7 + /** 8 + * A scheduler implementation that can hold multiple timer instances 9 + * 10 + * This class should only be used internally, see TimerInterface instead. 11 + * 12 + * @see TimerInterface 13 + * @internal 14 + */ 15 + final class Timers 16 + { 17 + private $time; 18 + private $timers = array(); 19 + private $schedule = array(); 20 + private $sorted = true; 21 + private $useHighResolution; 22 + 23 + public function __construct() 24 + { 25 + // prefer high-resolution timer, available as of PHP 7.3+ 26 + $this->useHighResolution = \function_exists('hrtime'); 27 + } 28 + 29 + public function updateTime() 30 + { 31 + return $this->time = $this->useHighResolution ? \hrtime(true) * 1e-9 : \microtime(true); 32 + } 33 + 34 + public function getTime() 35 + { 36 + return $this->time ?: $this->updateTime(); 37 + } 38 + 39 + public function add(TimerInterface $timer) 40 + { 41 + $id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer); 42 + $this->timers[$id] = $timer; 43 + $this->schedule[$id] = $timer->getInterval() + $this->updateTime(); 44 + $this->sorted = false; 45 + } 46 + 47 + public function contains(TimerInterface $timer) 48 + { 49 + $id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer); 50 + return isset($this->timers[$id]); 51 + } 52 + 53 + public function cancel(TimerInterface $timer) 54 + { 55 + $id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer); 56 + unset($this->timers[$id], $this->schedule[$id]); 57 + } 58 + 59 + public function getFirst() 60 + { 61 + // ensure timers are sorted to simply accessing next (first) one 62 + if (!$this->sorted) { 63 + $this->sorted = true; 64 + \asort($this->schedule); 65 + } 66 + 67 + return \reset($this->schedule); 68 + } 69 + 70 + public function isEmpty() 71 + { 72 + return \count($this->timers) === 0; 73 + } 74 + 75 + public function tick() 76 + { 77 + // hot path: skip timers if nothing is scheduled 78 + if (!$this->schedule) { 79 + return; 80 + } 81 + 82 + // ensure timers are sorted so we can execute in order 83 + if (!$this->sorted) { 84 + $this->sorted = true; 85 + \asort($this->schedule); 86 + } 87 + 88 + $time = $this->updateTime(); 89 + 90 + foreach ($this->schedule as $id => $scheduled) { 91 + // schedule is ordered, so loop until first timer that is not scheduled for execution now 92 + if ($scheduled >= $time) { 93 + break; 94 + } 95 + 96 + // skip any timers that are removed while we process the current schedule 97 + if (!isset($this->schedule[$id]) || $this->schedule[$id] !== $scheduled) { 98 + continue; 99 + } 100 + 101 + $timer = $this->timers[$id]; 102 + \call_user_func($timer->getCallback(), $timer); 103 + 104 + // re-schedule if this is a periodic timer and it has not been cancelled explicitly already 105 + if ($timer->isPeriodic() && isset($this->timers[$id])) { 106 + $this->schedule[$id] = $timer->getInterval() + $time; 107 + $this->sorted = false; 108 + } else { 109 + unset($this->timers[$id], $this->schedule[$id]); 110 + } 111 + } 112 + } 113 + }
+27
vendor/react/event-loop/src/TimerInterface.php
··· 1 + <?php 2 + 3 + namespace React\EventLoop; 4 + 5 + interface TimerInterface 6 + { 7 + /** 8 + * Get the interval after which this timer will execute, in seconds 9 + * 10 + * @return float 11 + */ 12 + public function getInterval(); 13 + 14 + /** 15 + * Get the callback that will be executed when this timer elapses 16 + * 17 + * @return callable 18 + */ 19 + public function getCallback(); 20 + 21 + /** 22 + * Determine whether the time is periodic 23 + * 24 + * @return bool 25 + */ 26 + public function isPeriodic(); 27 + }
+156
vendor/react/promise/CHANGELOG.md
··· 1 + # Changelog 2 + 3 + ## 3.2.0 (2024-05-24) 4 + 5 + * Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations. 6 + (#260 by @Ayesh) 7 + 8 + * Feature: Include previous exceptions when reporting unhandled promise rejections. 9 + (#262 by @clue) 10 + 11 + * Update test suite to improve PHP 8.4+ support. 12 + (#261 by @SimonFrings) 13 + 14 + ## 3.1.0 (2023-11-16) 15 + 16 + * Feature: Full PHP 8.3 compatibility. 17 + (#255 by @clue) 18 + 19 + * Feature: Describe all callable arguments with types for `Promise` and `Deferred`. 20 + (#253 by @clue) 21 + 22 + * Update test suite and minor documentation improvements. 23 + (#251 by @ondrejmirtes and #250 by @SQKo) 24 + 25 + ## 3.0.0 (2023-07-11) 26 + 27 + A major new feature release, see [**release announcement**](https://clue.engineering/2023/announcing-reactphp-promise-v3). 28 + 29 + * We'd like to emphasize that this component is production ready and battle-tested. 30 + We plan to support all long-term support (LTS) releases for at least 24 months, 31 + so you have a rock-solid foundation to build on top of. 32 + 33 + * The v3 release will be the way forward for this package. However, we will still 34 + actively support v2 and v1 to provide a smooth upgrade path for those not yet 35 + on the latest versions. 36 + 37 + This update involves some major new features and a minor BC break over the 38 + `v2.0.0` release. We've tried hard to avoid BC breaks where possible and 39 + minimize impact otherwise. We expect that most consumers of this package will be 40 + affected by BC breaks, but updating should take no longer than a few minutes. 41 + See below for more details: 42 + 43 + * BC break: PHP 8.1+ recommended, PHP 7.1+ required. 44 + (#138 and #149 by @WyriHaximus) 45 + 46 + * Feature / BC break: The `PromiseInterface` now includes the functionality of the old ~~`ExtendedPromiseInterface`~~ and ~~`CancellablePromiseInterface`~~. 47 + Each promise now always includes the `then()`, `catch()`, `finally()` and `cancel()` methods. 48 + The new `catch()` and `finally()` methods replace the deprecated ~~`otherwise()`~~ and ~~`always()`~~ methods which continue to exist for BC reasons. 49 + The old ~~`ExtendedPromiseInterface`~~ and ~~`CancellablePromiseInterface`~~ are no longer needed and have been removed as a consequence. 50 + (#75 by @jsor and #208 by @clue and @WyriHaximus) 51 + 52 + ```php 53 + // old (multiple interfaces may or may not be implemented) 54 + assert($promise instanceof PromiseInterface); 55 + assert(method_exists($promise, 'then')); 56 + if ($promise instanceof ExtendedPromiseInterface) { assert(method_exists($promise, 'otherwise')); } 57 + if ($promise instanceof ExtendedPromiseInterface) { assert(method_exists($promise, 'always')); } 58 + if ($promise instanceof CancellablePromiseInterface) { assert(method_exists($promise, 'cancel')); } 59 + 60 + // new (single PromiseInterface with all methods) 61 + assert($promise instanceof PromiseInterface); 62 + assert(method_exists($promise, 'then')); 63 + assert(method_exists($promise, 'catch')); 64 + assert(method_exists($promise, 'finally')); 65 + assert(method_exists($promise, 'cancel')); 66 + ``` 67 + 68 + * Feature / BC break: Improve type safety of promises. Require `mixed` fulfillment value argument and `Throwable` (or `Exception`) as rejection reason. 69 + Add PHPStan template types to ensure strict types for `resolve(T $value): PromiseInterface<T>` and `reject(Throwable $reason): PromiseInterface<never>`. 70 + 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). 71 + (#93, #141 and #142 by @jsor, #138, #149 and #247 by @WyriHaximus and #213 and #246 by @clue) 72 + 73 + ```php 74 + // old (arguments used to be optional) 75 + $promise = resolve(); 76 + $promise = reject(); 77 + 78 + // new (already supported before) 79 + $promise = resolve(null); 80 + $promise = reject(new RuntimeException()); 81 + ``` 82 + 83 + * Feature / BC break: Report all unhandled rejections by default and remove ~~`done()`~~ method. 84 + Add new `set_rejection_handler()` function to set the global rejection handler for unhandled promise rejections. 85 + (#248, #249 and #224 by @clue) 86 + 87 + ```php 88 + // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2 89 + reject(new RuntimeException('Unhandled')); 90 + ``` 91 + 92 + * BC break: Remove all deprecated APIs and reduce API surface. 93 + Remove ~~`some()`~~, ~~`map()`~~, ~~`reduce()`~~ functions, use `any()` and `all()` functions instead. 94 + Remove internal ~~`FulfilledPromise`~~ and ~~`RejectedPromise`~~ classes, use `resolve()` and `reject()` functions instead. 95 + Remove legacy promise progress API (deprecated third argument to `then()` method) and deprecated ~~`LazyPromise`~~ class. 96 + (#32 and #98 by @jsor and #164, #219 and #220 by @clue) 97 + 98 + * BC break: Make all classes final to encourage composition over inheritance. 99 + (#80 by @jsor) 100 + 101 + * Feature / BC break: Require `array` (or `iterable`) type for `all()` + `race()` + `any()` functions and bring in line with ES6 specification. 102 + These functions now require a single argument with a variable number of promises or values as input. 103 + (#225 by @clue and #35 by @jsor) 104 + 105 + * 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. 106 + (#83 by @jsor and #225 by @clue) 107 + 108 + * Minor performance improvements by initializing `Deferred` in the constructor and avoiding `call_user_func()` calls. 109 + (#151 by @WyriHaximus and #171 by @Kubo2) 110 + 111 + * Minor documentation improvements. 112 + (#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) 113 + 114 + The following changes had to be ported to this release due to our branching 115 + strategy, but also appeared in the [`2.x` branch](https://github.com/reactphp/promise/tree/2.x): 116 + 117 + * Feature: Support union types and address deprecation of `ReflectionType::getClass()` (PHP 8+). 118 + (#197 by @cdosoftei and @SimonFrings) 119 + 120 + * Feature: Support intersection types (PHP 8.1+). 121 + (#209 by @bzikarsky) 122 + 123 + * Feature: Support DNS types (PHP 8.2+). 124 + (#236 by @nhedger) 125 + 126 + * Feature: Port all memory improvements from `2.x` to `3.x`. 127 + (#150 by @clue and @WyriHaximus) 128 + 129 + * Fix: Fix checking whether cancellable promise is an object and avoid possible warning. 130 + (#161 by @smscr) 131 + 132 + * Improve performance by prefixing all global functions calls with \ to skip the look up and resolve process and go straight to the global function. 133 + (#134 by @WyriHaximus) 134 + 135 + * Improve test suite, update PHPUnit and PHP versions and add `.gitattributes` to exclude dev files from exports. 136 + (#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) 137 + 138 + The following changes were originally planned for this release but later reverted 139 + and are not part of the final release: 140 + 141 + * Add iterative callback queue handler to avoid recursion (later removed to improve Fiber support). 142 + (#28, #82 and #86 by @jsor, #158 by @WyriHaximus and #229 and #238 by @clue) 143 + 144 + * Trigger an `E_USER_ERROR` instead of throwing an exception from `done()` (later removed entire `done()` method to globally report unhandled rejections). 145 + (#97 by @jsor and #224 and #248 by @clue) 146 + 147 + * Add type declarations for `some()` (later removed entire `some()` function). 148 + (#172 by @WyriHaximus and #219 by @clue) 149 + 150 + ## 2.0.0 (2013-12-10) 151 + 152 + See [`2.x` CHANGELOG](https://github.com/reactphp/promise/blob/2.x/CHANGELOG.md) for more details. 153 + 154 + ## 1.0.0 (2012-11-07) 155 + 156 + See [`1.x` CHANGELOG](https://github.com/reactphp/promise/blob/1.x/CHANGELOG.md) for more details.
+24
vendor/react/promise/LICENSE
··· 1 + The MIT License (MIT) 2 + 3 + Copyright (c) 2012 Jan Sorgalla, Christian Lück, Cees-Jan Kiewiet, Chris Boden 4 + 5 + Permission is hereby granted, free of charge, to any person 6 + obtaining a copy of this software and associated documentation 7 + files (the "Software"), to deal in the Software without 8 + restriction, including without limitation the rights to use, 9 + copy, modify, merge, publish, distribute, sublicense, and/or sell 10 + copies of the Software, and to permit persons to whom the 11 + Software is furnished to do so, subject to the following 12 + conditions: 13 + 14 + The above copyright notice and this permission notice shall be 15 + included in all copies or substantial portions of the Software. 16 + 17 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 + OTHER DEALINGS IN THE SOFTWARE.
+722
vendor/react/promise/README.md
··· 1 + Promise 2 + ======= 3 + 4 + A lightweight implementation of 5 + [CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP. 6 + 7 + [![CI status](https://github.com/reactphp/promise/workflows/CI/badge.svg)](https://github.com/reactphp/promise/actions) 8 + [![installs on Packagist](https://img.shields.io/packagist/dt/react/promise?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/promise) 9 + 10 + Table of Contents 11 + ----------------- 12 + 13 + 1. [Introduction](#introduction) 14 + 2. [Concepts](#concepts) 15 + * [Deferred](#deferred) 16 + * [Promise](#promise-1) 17 + 3. [API](#api) 18 + * [Deferred](#deferred-1) 19 + * [Deferred::promise()](#deferredpromise) 20 + * [Deferred::resolve()](#deferredresolve) 21 + * [Deferred::reject()](#deferredreject) 22 + * [PromiseInterface](#promiseinterface) 23 + * [PromiseInterface::then()](#promiseinterfacethen) 24 + * [PromiseInterface::catch()](#promiseinterfacecatch) 25 + * [PromiseInterface::finally()](#promiseinterfacefinally) 26 + * [PromiseInterface::cancel()](#promiseinterfacecancel) 27 + * [~~PromiseInterface::otherwise()~~](#promiseinterfaceotherwise) 28 + * [~~PromiseInterface::always()~~](#promiseinterfacealways) 29 + * [Promise](#promise-2) 30 + * [Functions](#functions) 31 + * [resolve()](#resolve) 32 + * [reject()](#reject) 33 + * [all()](#all) 34 + * [race()](#race) 35 + * [any()](#any) 36 + * [set_rejection_handler()](#set_rejection_handler) 37 + 4. [Examples](#examples) 38 + * [How to use Deferred](#how-to-use-deferred) 39 + * [How promise forwarding works](#how-promise-forwarding-works) 40 + * [Resolution forwarding](#resolution-forwarding) 41 + * [Rejection forwarding](#rejection-forwarding) 42 + * [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding) 43 + 5. [Install](#install) 44 + 6. [Tests](#tests) 45 + 7. [Credits](#credits) 46 + 8. [License](#license) 47 + 48 + Introduction 49 + ------------ 50 + 51 + Promise is a library implementing 52 + [CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP. 53 + 54 + It also provides several other useful promise-related concepts, such as joining 55 + multiple promises and mapping and reducing collections of promises. 56 + 57 + If you've never heard about promises before, 58 + [read this first](https://gist.github.com/domenic/3889970). 59 + 60 + Concepts 61 + -------- 62 + 63 + ### Deferred 64 + 65 + A **Deferred** represents a computation or unit of work that may not have 66 + completed yet. Typically (but not always), that computation will be something 67 + that executes asynchronously and completes at some point in the future. 68 + 69 + ### Promise 70 + 71 + While a deferred represents the computation itself, a **Promise** represents 72 + the result of that computation. Thus, each deferred has a promise that acts as 73 + a placeholder for its actual result. 74 + 75 + API 76 + --- 77 + 78 + ### Deferred 79 + 80 + A deferred represents an operation whose resolution is pending. It has separate 81 + promise and resolver parts. 82 + 83 + ```php 84 + $deferred = new React\Promise\Deferred(); 85 + 86 + $promise = $deferred->promise(); 87 + 88 + $deferred->resolve(mixed $value); 89 + $deferred->reject(\Throwable $reason); 90 + ``` 91 + 92 + The `promise` method returns the promise of the deferred. 93 + 94 + The `resolve` and `reject` methods control the state of the deferred. 95 + 96 + The constructor of the `Deferred` accepts an optional `$canceller` argument. 97 + See [Promise](#promise-2) for more information. 98 + 99 + #### Deferred::promise() 100 + 101 + ```php 102 + $promise = $deferred->promise(); 103 + ``` 104 + 105 + Returns the promise of the deferred, which you can hand out to others while 106 + keeping the authority to modify its state to yourself. 107 + 108 + #### Deferred::resolve() 109 + 110 + ```php 111 + $deferred->resolve(mixed $value); 112 + ``` 113 + 114 + Resolves the promise returned by `promise()`. All consumers are notified by 115 + having `$onFulfilled` (which they registered via `$promise->then()`) called with 116 + `$value`. 117 + 118 + If `$value` itself is a promise, the promise will transition to the state of 119 + this promise once it is resolved. 120 + 121 + See also the [`resolve()` function](#resolve). 122 + 123 + #### Deferred::reject() 124 + 125 + ```php 126 + $deferred->reject(\Throwable $reason); 127 + ``` 128 + 129 + Rejects the promise returned by `promise()`, signalling that the deferred's 130 + computation failed. 131 + All consumers are notified by having `$onRejected` (which they registered via 132 + `$promise->then()`) called with `$reason`. 133 + 134 + See also the [`reject()` function](#reject). 135 + 136 + ### PromiseInterface 137 + 138 + The promise interface provides the common interface for all promise 139 + implementations. 140 + See [Promise](#promise-2) for the only public implementation exposed by this 141 + package. 142 + 143 + A promise represents an eventual outcome, which is either fulfillment (success) 144 + and an associated value, or rejection (failure) and an associated reason. 145 + 146 + Once in the fulfilled or rejected state, a promise becomes immutable. 147 + Neither its state nor its result (or error) can be modified. 148 + 149 + #### PromiseInterface::then() 150 + 151 + ```php 152 + $transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null); 153 + ``` 154 + 155 + Transforms a promise's value by applying a function to the promise's fulfillment 156 + or rejection value. Returns a new promise for the transformed result. 157 + 158 + The `then()` method registers new fulfilled and rejection handlers with a promise 159 + (all parameters are optional): 160 + 161 + * `$onFulfilled` will be invoked once the promise is fulfilled and passed 162 + the result as the first argument. 163 + * `$onRejected` will be invoked once the promise is rejected and passed the 164 + reason as the first argument. 165 + 166 + It returns a new promise that will fulfill with the return value of either 167 + `$onFulfilled` or `$onRejected`, whichever is called, or will reject with 168 + the thrown exception if either throws. 169 + 170 + A promise makes the following guarantees about handlers registered in 171 + the same call to `then()`: 172 + 173 + 1. Only one of `$onFulfilled` or `$onRejected` will be called, 174 + never both. 175 + 2. `$onFulfilled` and `$onRejected` will never be called more 176 + than once. 177 + 178 + #### See also 179 + 180 + * [resolve()](#resolve) - Creating a resolved promise 181 + * [reject()](#reject) - Creating a rejected promise 182 + 183 + #### PromiseInterface::catch() 184 + 185 + ```php 186 + $promise->catch(callable $onRejected); 187 + ``` 188 + 189 + Registers a rejection handler for promise. It is a shortcut for: 190 + 191 + ```php 192 + $promise->then(null, $onRejected); 193 + ``` 194 + 195 + Additionally, you can type hint the `$reason` argument of `$onRejected` to catch 196 + only specific errors. 197 + 198 + ```php 199 + $promise 200 + ->catch(function (\RuntimeException $reason) { 201 + // Only catch \RuntimeException instances 202 + // All other types of errors will propagate automatically 203 + }) 204 + ->catch(function (\Throwable $reason) { 205 + // Catch other errors 206 + }); 207 + ``` 208 + 209 + #### PromiseInterface::finally() 210 + 211 + ```php 212 + $newPromise = $promise->finally(callable $onFulfilledOrRejected); 213 + ``` 214 + 215 + Allows you to execute "cleanup" type tasks in a promise chain. 216 + 217 + It arranges for `$onFulfilledOrRejected` to be called, with no arguments, 218 + when the promise is either fulfilled or rejected. 219 + 220 + * If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully, 221 + `$newPromise` will fulfill with the same value as `$promise`. 222 + * If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a 223 + rejected promise, `$newPromise` will reject with the thrown exception or 224 + rejected promise's reason. 225 + * If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully, 226 + `$newPromise` will reject with the same reason as `$promise`. 227 + * If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a 228 + rejected promise, `$newPromise` will reject with the thrown exception or 229 + rejected promise's reason. 230 + 231 + `finally()` behaves similarly to the synchronous finally statement. When combined 232 + with `catch()`, `finally()` allows you to write code that is similar to the familiar 233 + synchronous catch/finally pair. 234 + 235 + Consider the following synchronous code: 236 + 237 + ```php 238 + try { 239 + return doSomething(); 240 + } catch (\Throwable $e) { 241 + return handleError($e); 242 + } finally { 243 + cleanup(); 244 + } 245 + ``` 246 + 247 + Similar asynchronous code (with `doSomething()` that returns a promise) can be 248 + written: 249 + 250 + ```php 251 + return doSomething() 252 + ->catch('handleError') 253 + ->finally('cleanup'); 254 + ``` 255 + 256 + #### PromiseInterface::cancel() 257 + 258 + ``` php 259 + $promise->cancel(); 260 + ``` 261 + 262 + The `cancel()` method notifies the creator of the promise that there is no 263 + further interest in the results of the operation. 264 + 265 + Once a promise is settled (either fulfilled or rejected), calling `cancel()` on 266 + a promise has no effect. 267 + 268 + #### ~~PromiseInterface::otherwise()~~ 269 + 270 + > Deprecated since v3.0.0, see [`catch()`](#promiseinterfacecatch) instead. 271 + 272 + The `otherwise()` method registers a rejection handler for a promise. 273 + 274 + This method continues to exist only for BC reasons and to ease upgrading 275 + between versions. It is an alias for: 276 + 277 + ```php 278 + $promise->catch($onRejected); 279 + ``` 280 + 281 + #### ~~PromiseInterface::always()~~ 282 + 283 + > Deprecated since v3.0.0, see [`finally()`](#promiseinterfacefinally) instead. 284 + 285 + The `always()` method allows you to execute "cleanup" type tasks in a promise chain. 286 + 287 + This method continues to exist only for BC reasons and to ease upgrading 288 + between versions. It is an alias for: 289 + 290 + ```php 291 + $promise->finally($onFulfilledOrRejected); 292 + ``` 293 + 294 + ### Promise 295 + 296 + Creates a promise whose state is controlled by the functions passed to 297 + `$resolver`. 298 + 299 + ```php 300 + $resolver = function (callable $resolve, callable $reject) { 301 + // Do some work, possibly asynchronously, and then 302 + // resolve or reject. 303 + 304 + $resolve($awesomeResult); 305 + // or throw new Exception('Promise rejected'); 306 + // or $resolve($anotherPromise); 307 + // or $reject($nastyError); 308 + }; 309 + 310 + $canceller = function () { 311 + // Cancel/abort any running operations like network connections, streams etc. 312 + 313 + // Reject promise by throwing an exception 314 + throw new Exception('Promise cancelled'); 315 + }; 316 + 317 + $promise = new React\Promise\Promise($resolver, $canceller); 318 + ``` 319 + 320 + The promise constructor receives a resolver function and an optional canceller 321 + function which both will be called with two arguments: 322 + 323 + * `$resolve($value)` - Primary function that seals the fate of the 324 + returned promise. Accepts either a non-promise value, or another promise. 325 + When called with a non-promise value, fulfills promise with that value. 326 + When called with another promise, e.g. `$resolve($otherPromise)`, promise's 327 + fate will be equivalent to that of `$otherPromise`. 328 + * `$reject($reason)` - Function that rejects the promise. It is recommended to 329 + just throw an exception instead of using `$reject()`. 330 + 331 + If the resolver or canceller throw an exception, the promise will be rejected 332 + with that thrown exception as the rejection reason. 333 + 334 + The resolver function will be called immediately, the canceller function only 335 + once all consumers called the `cancel()` method of the promise. 336 + 337 + ### Functions 338 + 339 + Useful functions for creating and joining collections of promises. 340 + 341 + All functions working on promise collections (like `all()`, `race()`, 342 + etc.) support cancellation. This means, if you call `cancel()` on the returned 343 + promise, all promises in the collection are cancelled. 344 + 345 + #### resolve() 346 + 347 + ```php 348 + $promise = React\Promise\resolve(mixed $promiseOrValue); 349 + ``` 350 + 351 + Creates a promise for the supplied `$promiseOrValue`. 352 + 353 + If `$promiseOrValue` is a value, it will be the resolution value of the 354 + returned promise. 355 + 356 + If `$promiseOrValue` is a thenable (any object that provides a `then()` method), 357 + a trusted promise that follows the state of the thenable is returned. 358 + 359 + If `$promiseOrValue` is a promise, it will be returned as is. 360 + 361 + The resulting `$promise` implements the [`PromiseInterface`](#promiseinterface) 362 + and can be consumed like any other promise: 363 + 364 + ```php 365 + $promise = React\Promise\resolve(42); 366 + 367 + $promise->then(function (int $result): void { 368 + var_dump($result); 369 + }, function (\Throwable $e): void { 370 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 371 + }); 372 + ``` 373 + 374 + #### reject() 375 + 376 + ```php 377 + $promise = React\Promise\reject(\Throwable $reason); 378 + ``` 379 + 380 + Creates a rejected promise for the supplied `$reason`. 381 + 382 + Note that the [`\Throwable`](https://www.php.net/manual/en/class.throwable.php) interface introduced in PHP 7 covers 383 + both user land [`\Exception`](https://www.php.net/manual/en/class.exception.php)'s and 384 + [`\Error`](https://www.php.net/manual/en/class.error.php) internal PHP errors. By enforcing `\Throwable` as reason to 385 + reject a promise, any language error or user land exception can be used to reject a promise. 386 + 387 + The resulting `$promise` implements the [`PromiseInterface`](#promiseinterface) 388 + and can be consumed like any other promise: 389 + 390 + ```php 391 + $promise = React\Promise\reject(new RuntimeException('Request failed')); 392 + 393 + $promise->then(function (int $result): void { 394 + var_dump($result); 395 + }, function (\Throwable $e): void { 396 + echo 'Error: ' . $e->getMessage() . PHP_EOL; 397 + }); 398 + ``` 399 + 400 + Note that rejected promises should always be handled similar to how any 401 + exceptions should always be caught in a `try` + `catch` block. If you remove the 402 + last reference to a rejected promise that has not been handled, it will 403 + report an unhandled promise rejection: 404 + 405 + ```php 406 + function incorrect(): int 407 + { 408 + $promise = React\Promise\reject(new RuntimeException('Request failed')); 409 + 410 + // Commented out: No rejection handler registered here. 411 + // $promise->then(null, function (\Throwable $e): void { /* ignore */ }); 412 + 413 + // Returning from a function will remove all local variable references, hence why 414 + // this will report an unhandled promise rejection here. 415 + return 42; 416 + } 417 + 418 + // Calling this function will log an error message plus its stack trace: 419 + // Unhandled promise rejection with RuntimeException: Request failed in example.php:10 420 + incorrect(); 421 + ``` 422 + 423 + A rejected promise will be considered "handled" if you catch the rejection 424 + reason with either the [`then()` method](#promiseinterfacethen), the 425 + [`catch()` method](#promiseinterfacecatch), or the 426 + [`finally()` method](#promiseinterfacefinally). Note that each of these methods 427 + return a new promise that may again be rejected if you re-throw an exception. 428 + 429 + A rejected promise will also be considered "handled" if you abort the operation 430 + with the [`cancel()` method](#promiseinterfacecancel) (which in turn would 431 + usually reject the promise if it is still pending). 432 + 433 + See also the [`set_rejection_handler()` function](#set_rejection_handler). 434 + 435 + #### all() 436 + 437 + ```php 438 + $promise = React\Promise\all(iterable $promisesOrValues); 439 + ``` 440 + 441 + Returns a promise that will resolve only once all the items in 442 + `$promisesOrValues` have resolved. The resolution value of the returned promise 443 + will be an array containing the resolution values of each of the items in 444 + `$promisesOrValues`. 445 + 446 + #### race() 447 + 448 + ```php 449 + $promise = React\Promise\race(iterable $promisesOrValues); 450 + ``` 451 + 452 + Initiates a competitive race that allows one winner. Returns a promise which is 453 + resolved in the same way the first settled promise resolves. 454 + 455 + The returned promise will become **infinitely pending** if `$promisesOrValues` 456 + contains 0 items. 457 + 458 + #### any() 459 + 460 + ```php 461 + $promise = React\Promise\any(iterable $promisesOrValues); 462 + ``` 463 + 464 + Returns a promise that will resolve when any one of the items in 465 + `$promisesOrValues` resolves. The resolution value of the returned promise 466 + will be the resolution value of the triggering item. 467 + 468 + The returned promise will only reject if *all* items in `$promisesOrValues` are 469 + rejected. The rejection value will be a `React\Promise\Exception\CompositeException` 470 + which holds all rejection reasons. The rejection reasons can be obtained with 471 + `CompositeException::getThrowables()`. 472 + 473 + The returned promise will also reject with a `React\Promise\Exception\LengthException` 474 + if `$promisesOrValues` contains 0 items. 475 + 476 + #### set_rejection_handler() 477 + 478 + ```php 479 + React\Promise\set_rejection_handler(?callable $callback): ?callable; 480 + ``` 481 + 482 + Sets the global rejection handler for unhandled promise rejections. 483 + 484 + Note that rejected promises should always be handled similar to how any 485 + exceptions should always be caught in a `try` + `catch` block. If you remove 486 + the last reference to a rejected promise that has not been handled, it will 487 + report an unhandled promise rejection. See also the [`reject()` function](#reject) 488 + for more details. 489 + 490 + The `?callable $callback` argument MUST be a valid callback function that 491 + accepts a single `Throwable` argument or a `null` value to restore the 492 + default promise rejection handler. The return value of the callback function 493 + will be ignored and has no effect, so you SHOULD return a `void` value. The 494 + callback function MUST NOT throw or the program will be terminated with a 495 + fatal error. 496 + 497 + The function returns the previous rejection handler or `null` if using the 498 + default promise rejection handler. 499 + 500 + The default promise rejection handler will log an error message plus its stack 501 + trace: 502 + 503 + ```php 504 + // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2 505 + React\Promise\reject(new RuntimeException('Unhandled')); 506 + ``` 507 + 508 + The promise rejection handler may be used to use customize the log message or 509 + write to custom log targets. As a rule of thumb, this function should only be 510 + used as a last resort and promise rejections are best handled with either the 511 + [`then()` method](#promiseinterfacethen), the 512 + [`catch()` method](#promiseinterfacecatch), or the 513 + [`finally()` method](#promiseinterfacefinally). 514 + See also the [`reject()` function](#reject) for more details. 515 + 516 + Examples 517 + -------- 518 + 519 + ### How to use Deferred 520 + 521 + ```php 522 + function getAwesomeResultPromise() 523 + { 524 + $deferred = new React\Promise\Deferred(); 525 + 526 + // Execute a Node.js-style function using the callback pattern 527 + computeAwesomeResultAsynchronously(function (\Throwable $error, $result) use ($deferred) { 528 + if ($error) { 529 + $deferred->reject($error); 530 + } else { 531 + $deferred->resolve($result); 532 + } 533 + }); 534 + 535 + // Return the promise 536 + return $deferred->promise(); 537 + } 538 + 539 + getAwesomeResultPromise() 540 + ->then( 541 + function ($value) { 542 + // Deferred resolved, do something with $value 543 + }, 544 + function (\Throwable $reason) { 545 + // Deferred rejected, do something with $reason 546 + } 547 + ); 548 + ``` 549 + 550 + ### How promise forwarding works 551 + 552 + A few simple examples to show how the mechanics of Promises/A forwarding works. 553 + These examples are contrived, of course, and in real usage, promise chains will 554 + typically be spread across several function calls, or even several levels of 555 + your application architecture. 556 + 557 + #### Resolution forwarding 558 + 559 + Resolved promises forward resolution values to the next promise. 560 + The first promise, `$deferred->promise()`, will resolve with the value passed 561 + to `$deferred->resolve()` below. 562 + 563 + Each call to `then()` returns a new promise that will resolve with the return 564 + value of the previous handler. This creates a promise "pipeline". 565 + 566 + ```php 567 + $deferred = new React\Promise\Deferred(); 568 + 569 + $deferred->promise() 570 + ->then(function ($x) { 571 + // $x will be the value passed to $deferred->resolve() below 572 + // and returns a *new promise* for $x + 1 573 + return $x + 1; 574 + }) 575 + ->then(function ($x) { 576 + // $x === 2 577 + // This handler receives the return value of the 578 + // previous handler. 579 + return $x + 1; 580 + }) 581 + ->then(function ($x) { 582 + // $x === 3 583 + // This handler receives the return value of the 584 + // previous handler. 585 + return $x + 1; 586 + }) 587 + ->then(function ($x) { 588 + // $x === 4 589 + // This handler receives the return value of the 590 + // previous handler. 591 + echo 'Resolve ' . $x; 592 + }); 593 + 594 + $deferred->resolve(1); // Prints "Resolve 4" 595 + ``` 596 + 597 + #### Rejection forwarding 598 + 599 + Rejected promises behave similarly, and also work similarly to try/catch: 600 + When you catch an exception, you must rethrow for it to propagate. 601 + 602 + Similarly, when you handle a rejected promise, to propagate the rejection, 603 + "rethrow" it by either returning a rejected promise, or actually throwing 604 + (since promise translates thrown exceptions into rejections) 605 + 606 + ```php 607 + $deferred = new React\Promise\Deferred(); 608 + 609 + $deferred->promise() 610 + ->then(function ($x) { 611 + throw new \Exception($x + 1); 612 + }) 613 + ->catch(function (\Exception $x) { 614 + // Propagate the rejection 615 + throw $x; 616 + }) 617 + ->catch(function (\Exception $x) { 618 + // Can also propagate by returning another rejection 619 + return React\Promise\reject( 620 + new \Exception($x->getMessage() + 1) 621 + ); 622 + }) 623 + ->catch(function ($x) { 624 + echo 'Reject ' . $x->getMessage(); // 3 625 + }); 626 + 627 + $deferred->resolve(1); // Prints "Reject 3" 628 + ``` 629 + 630 + #### Mixed resolution and rejection forwarding 631 + 632 + Just like try/catch, you can choose to propagate or not. Mixing resolutions and 633 + rejections will still forward handler results in a predictable way. 634 + 635 + ```php 636 + $deferred = new React\Promise\Deferred(); 637 + 638 + $deferred->promise() 639 + ->then(function ($x) { 640 + return $x + 1; 641 + }) 642 + ->then(function ($x) { 643 + throw new \Exception($x + 1); 644 + }) 645 + ->catch(function (\Exception $x) { 646 + // Handle the rejection, and don't propagate. 647 + // This is like catch without a rethrow 648 + return $x->getMessage() + 1; 649 + }) 650 + ->then(function ($x) { 651 + echo 'Mixed ' . $x; // 4 652 + }); 653 + 654 + $deferred->resolve(1); // Prints "Mixed 4" 655 + ``` 656 + 657 + Install 658 + ------- 659 + 660 + The recommended way to install this library is [through Composer](https://getcomposer.org/). 661 + [New to Composer?](https://getcomposer.org/doc/00-intro.md) 662 + 663 + This project follows [SemVer](https://semver.org/). 664 + This will install the latest supported version from this branch: 665 + 666 + ```bash 667 + composer require react/promise:^3.2 668 + ``` 669 + 670 + See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. 671 + 672 + This project aims to run on any platform and thus does not require any PHP 673 + extensions and supports running on PHP 7.1 through current PHP 8+. 674 + It's *highly recommended to use the latest supported PHP version* for this project. 675 + 676 + We're committed to providing long-term support (LTS) options and to provide a 677 + smooth upgrade path. If you're using an older PHP version, you may use the 678 + [`2.x` branch](https://github.com/reactphp/promise/tree/2.x) (PHP 5.4+) or 679 + [`1.x` branch](https://github.com/reactphp/promise/tree/1.x) (PHP 5.3+) which both 680 + provide a compatible API but do not take advantage of newer language features. 681 + You may target multiple versions at the same time to support a wider range of 682 + PHP versions like this: 683 + 684 + ```bash 685 + composer require "react/promise:^3 || ^2 || ^1" 686 + ``` 687 + 688 + ## Tests 689 + 690 + To run the test suite, you first need to clone this repo and then install all 691 + dependencies [through Composer](https://getcomposer.org/): 692 + 693 + ```bash 694 + composer install 695 + ``` 696 + 697 + To run the test suite, go to the project root and run: 698 + 699 + ```bash 700 + vendor/bin/phpunit 701 + ``` 702 + 703 + On top of this, we use PHPStan on max level to ensure type safety across the project: 704 + 705 + ```bash 706 + vendor/bin/phpstan 707 + ``` 708 + 709 + Credits 710 + ------- 711 + 712 + Promise is a port of [when.js](https://github.com/cujojs/when) 713 + by [Brian Cavalier](https://github.com/briancavalier). 714 + 715 + Also, large parts of the documentation have been ported from the when.js 716 + [Wiki](https://github.com/cujojs/when/wiki) and the 717 + [API docs](https://github.com/cujojs/when/blob/master/docs/api.md). 718 + 719 + License 720 + ------- 721 + 722 + Released under the [MIT](LICENSE) license.
+57
vendor/react/promise/composer.json
··· 1 + { 2 + "name": "react/promise", 3 + "description": "A lightweight implementation of CommonJS Promises/A for PHP", 4 + "license": "MIT", 5 + "authors": [ 6 + { 7 + "name": "Jan Sorgalla", 8 + "homepage": "https://sorgalla.com/", 9 + "email": "jsorgalla@gmail.com" 10 + }, 11 + { 12 + "name": "Christian Lück", 13 + "homepage": "https://clue.engineering/", 14 + "email": "christian@clue.engineering" 15 + }, 16 + { 17 + "name": "Cees-Jan Kiewiet", 18 + "homepage": "https://wyrihaximus.net/", 19 + "email": "reactphp@ceesjankiewiet.nl" 20 + }, 21 + { 22 + "name": "Chris Boden", 23 + "homepage": "https://cboden.dev/", 24 + "email": "cboden@gmail.com" 25 + } 26 + ], 27 + "require": { 28 + "php": ">=7.1.0" 29 + }, 30 + "require-dev": { 31 + "phpstan/phpstan": "1.12.28 || 1.4.10", 32 + "phpunit/phpunit": "^9.6 || ^7.5" 33 + }, 34 + "autoload": { 35 + "psr-4": { 36 + "React\\Promise\\": "src/" 37 + }, 38 + "files": [ 39 + "src/functions_include.php" 40 + ] 41 + }, 42 + "autoload-dev": { 43 + "psr-4": { 44 + "React\\Promise\\": [ 45 + "tests/fixtures/", 46 + "tests/" 47 + ] 48 + }, 49 + "files": [ 50 + "tests/Fiber.php" 51 + ] 52 + }, 53 + "keywords": [ 54 + "promise", 55 + "promises" 56 + ] 57 + }
+52
vendor/react/promise/src/Deferred.php
··· 1 + <?php 2 + 3 + namespace React\Promise; 4 + 5 + /** 6 + * @template T 7 + */ 8 + final class Deferred 9 + { 10 + /** 11 + * @var PromiseInterface<T> 12 + */ 13 + private $promise; 14 + 15 + /** @var callable(T):void */ 16 + private $resolveCallback; 17 + 18 + /** @var callable(\Throwable):void */ 19 + private $rejectCallback; 20 + 21 + /** 22 + * @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller 23 + */ 24 + public function __construct(?callable $canceller = null) 25 + { 26 + $this->promise = new Promise(function ($resolve, $reject): void { 27 + $this->resolveCallback = $resolve; 28 + $this->rejectCallback = $reject; 29 + }, $canceller); 30 + } 31 + 32 + /** 33 + * @return PromiseInterface<T> 34 + */ 35 + public function promise(): PromiseInterface 36 + { 37 + return $this->promise; 38 + } 39 + 40 + /** 41 + * @param T $value 42 + */ 43 + public function resolve($value): void 44 + { 45 + ($this->resolveCallback)($value); 46 + } 47 + 48 + public function reject(\Throwable $reason): void 49 + { 50 + ($this->rejectCallback)($reason); 51 + } 52 + }
+32
vendor/react/promise/src/Exception/CompositeException.php
··· 1 + <?php 2 + 3 + namespace React\Promise\Exception; 4 + 5 + /** 6 + * Represents an exception that is a composite of one or more other exceptions. 7 + * 8 + * This exception is useful in situations where a promise must be rejected 9 + * with multiple exceptions. It is used for example to reject the returned 10 + * promise from `some()` and `any()` when too many input promises reject. 11 + */ 12 + class CompositeException extends \Exception 13 + { 14 + /** @var \Throwable[] */ 15 + private $throwables; 16 + 17 + /** @param \Throwable[] $throwables */ 18 + public function __construct(array $throwables, string $message = '', int $code = 0, ?\Throwable $previous = null) 19 + { 20 + parent::__construct($message, $code, $previous); 21 + 22 + $this->throwables = $throwables; 23 + } 24 + 25 + /** 26 + * @return \Throwable[] 27 + */ 28 + public function getThrowables(): array 29 + { 30 + return $this->throwables; 31 + } 32 + }
+7
vendor/react/promise/src/Exception/LengthException.php
··· 1 + <?php 2 + 3 + namespace React\Promise\Exception; 4 + 5 + class LengthException extends \LengthException 6 + { 7 + }
+64
vendor/react/promise/src/Internal/CancellationQueue.php
··· 1 + <?php 2 + 3 + namespace React\Promise\Internal; 4 + 5 + /** 6 + * @internal 7 + */ 8 + final class CancellationQueue 9 + { 10 + /** @var bool */ 11 + private $started = false; 12 + 13 + /** @var object[] */ 14 + private $queue = []; 15 + 16 + public function __invoke(): void 17 + { 18 + if ($this->started) { 19 + return; 20 + } 21 + 22 + $this->started = true; 23 + $this->drain(); 24 + } 25 + 26 + /** 27 + * @param mixed $cancellable 28 + */ 29 + public function enqueue($cancellable): void 30 + { 31 + if (!\is_object($cancellable) || !\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) { 32 + return; 33 + } 34 + 35 + $length = \array_push($this->queue, $cancellable); 36 + 37 + if ($this->started && 1 === $length) { 38 + $this->drain(); 39 + } 40 + } 41 + 42 + private function drain(): void 43 + { 44 + for ($i = \key($this->queue); isset($this->queue[$i]); $i++) { 45 + $cancellable = $this->queue[$i]; 46 + assert(\method_exists($cancellable, 'cancel')); 47 + 48 + $exception = null; 49 + 50 + try { 51 + $cancellable->cancel(); 52 + } catch (\Throwable $exception) { 53 + } 54 + 55 + unset($this->queue[$i]); 56 + 57 + if ($exception) { 58 + throw $exception; 59 + } 60 + } 61 + 62 + $this->queue = []; 63 + } 64 + }
+90
vendor/react/promise/src/Internal/FulfilledPromise.php
··· 1 + <?php 2 + 3 + namespace React\Promise\Internal; 4 + 5 + use React\Promise\PromiseInterface; 6 + use function React\Promise\resolve; 7 + 8 + /** 9 + * @internal 10 + * 11 + * @template T 12 + * @template-implements PromiseInterface<T> 13 + */ 14 + final class FulfilledPromise implements PromiseInterface 15 + { 16 + /** @var T */ 17 + private $value; 18 + 19 + /** 20 + * @param T $value 21 + * @throws \InvalidArgumentException 22 + */ 23 + public function __construct($value = null) 24 + { 25 + if ($value instanceof PromiseInterface) { 26 + throw new \InvalidArgumentException('You cannot create React\Promise\FulfilledPromise with a promise. Use React\Promise\resolve($promiseOrValue) instead.'); 27 + } 28 + 29 + $this->value = $value; 30 + } 31 + 32 + /** 33 + * @template TFulfilled 34 + * @param ?(callable((T is void ? null : T)): (PromiseInterface<TFulfilled>|TFulfilled)) $onFulfilled 35 + * @return PromiseInterface<($onFulfilled is null ? T : TFulfilled)> 36 + */ 37 + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface 38 + { 39 + if (null === $onFulfilled) { 40 + return $this; 41 + } 42 + 43 + try { 44 + /** 45 + * @var PromiseInterface<T>|T $result 46 + */ 47 + $result = $onFulfilled($this->value); 48 + return resolve($result); 49 + } catch (\Throwable $exception) { 50 + return new RejectedPromise($exception); 51 + } 52 + } 53 + 54 + public function catch(callable $onRejected): PromiseInterface 55 + { 56 + return $this; 57 + } 58 + 59 + public function finally(callable $onFulfilledOrRejected): PromiseInterface 60 + { 61 + return $this->then(function ($value) use ($onFulfilledOrRejected): PromiseInterface { 62 + /** @var T $value */ 63 + return resolve($onFulfilledOrRejected())->then(function () use ($value) { 64 + return $value; 65 + }); 66 + }); 67 + } 68 + 69 + public function cancel(): void 70 + { 71 + } 72 + 73 + /** 74 + * @deprecated 3.0.0 Use `catch()` instead 75 + * @see self::catch() 76 + */ 77 + public function otherwise(callable $onRejected): PromiseInterface 78 + { 79 + return $this->catch($onRejected); 80 + } 81 + 82 + /** 83 + * @deprecated 3.0.0 Use `finally()` instead 84 + * @see self::finally() 85 + */ 86 + public function always(callable $onFulfilledOrRejected): PromiseInterface 87 + { 88 + return $this->finally($onFulfilledOrRejected); 89 + } 90 + }
+128
vendor/react/promise/src/Internal/RejectedPromise.php
··· 1 + <?php 2 + 3 + namespace React\Promise\Internal; 4 + 5 + use React\Promise\PromiseInterface; 6 + use function React\Promise\_checkTypehint; 7 + use function React\Promise\resolve; 8 + use function React\Promise\set_rejection_handler; 9 + 10 + /** 11 + * @internal 12 + * 13 + * @template-implements PromiseInterface<never> 14 + */ 15 + final class RejectedPromise implements PromiseInterface 16 + { 17 + /** @var \Throwable */ 18 + private $reason; 19 + 20 + /** @var bool */ 21 + private $handled = false; 22 + 23 + /** 24 + * @param \Throwable $reason 25 + */ 26 + public function __construct(\Throwable $reason) 27 + { 28 + $this->reason = $reason; 29 + } 30 + 31 + /** @throws void */ 32 + public function __destruct() 33 + { 34 + if ($this->handled) { 35 + return; 36 + } 37 + 38 + $handler = set_rejection_handler(null); 39 + if ($handler === null) { 40 + $message = 'Unhandled promise rejection with ' . $this->reason; 41 + 42 + \error_log($message); 43 + return; 44 + } 45 + 46 + try { 47 + $handler($this->reason); 48 + } catch (\Throwable $e) { 49 + \preg_match('/^([^:\s]++)(.*+)$/sm', (string) $e, $match); 50 + \assert(isset($match[1], $match[2])); 51 + $message = 'Fatal error: Uncaught ' . $match[1] . ' from unhandled promise rejection handler' . $match[2]; 52 + 53 + \error_log($message); 54 + exit(255); 55 + } 56 + } 57 + 58 + /** 59 + * @template TRejected 60 + * @param ?callable $onFulfilled 61 + * @param ?(callable(\Throwable): (PromiseInterface<TRejected>|TRejected)) $onRejected 62 + * @return PromiseInterface<($onRejected is null ? never : TRejected)> 63 + */ 64 + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface 65 + { 66 + if (null === $onRejected) { 67 + return $this; 68 + } 69 + 70 + $this->handled = true; 71 + 72 + try { 73 + return resolve($onRejected($this->reason)); 74 + } catch (\Throwable $exception) { 75 + return new RejectedPromise($exception); 76 + } 77 + } 78 + 79 + /** 80 + * @template TThrowable of \Throwable 81 + * @template TRejected 82 + * @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected 83 + * @return PromiseInterface<TRejected> 84 + */ 85 + public function catch(callable $onRejected): PromiseInterface 86 + { 87 + if (!_checkTypehint($onRejected, $this->reason)) { 88 + return $this; 89 + } 90 + 91 + /** 92 + * @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected 93 + */ 94 + return $this->then(null, $onRejected); 95 + } 96 + 97 + public function finally(callable $onFulfilledOrRejected): PromiseInterface 98 + { 99 + return $this->then(null, function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface { 100 + return resolve($onFulfilledOrRejected())->then(function () use ($reason): PromiseInterface { 101 + return new RejectedPromise($reason); 102 + }); 103 + }); 104 + } 105 + 106 + public function cancel(): void 107 + { 108 + $this->handled = true; 109 + } 110 + 111 + /** 112 + * @deprecated 3.0.0 Use `catch()` instead 113 + * @see self::catch() 114 + */ 115 + public function otherwise(callable $onRejected): PromiseInterface 116 + { 117 + return $this->catch($onRejected); 118 + } 119 + 120 + /** 121 + * @deprecated 3.0.0 Use `always()` instead 122 + * @see self::always() 123 + */ 124 + public function always(callable $onFulfilledOrRejected): PromiseInterface 125 + { 126 + return $this->finally($onFulfilledOrRejected); 127 + } 128 + }
+304
vendor/react/promise/src/Promise.php
··· 1 + <?php 2 + 3 + namespace React\Promise; 4 + 5 + use React\Promise\Internal\RejectedPromise; 6 + 7 + /** 8 + * @template T 9 + * @template-implements PromiseInterface<T> 10 + */ 11 + final class Promise implements PromiseInterface 12 + { 13 + /** @var (callable(callable(T):void,callable(\Throwable):void):void)|null */ 14 + private $canceller; 15 + 16 + /** @var ?PromiseInterface<T> */ 17 + private $result; 18 + 19 + /** @var list<callable(PromiseInterface<T>):void> */ 20 + private $handlers = []; 21 + 22 + /** @var int */ 23 + private $requiredCancelRequests = 0; 24 + 25 + /** @var bool */ 26 + private $cancelled = false; 27 + 28 + /** 29 + * @param callable(callable(T):void,callable(\Throwable):void):void $resolver 30 + * @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller 31 + */ 32 + public function __construct(callable $resolver, ?callable $canceller = null) 33 + { 34 + $this->canceller = $canceller; 35 + 36 + // Explicitly overwrite arguments with null values before invoking 37 + // resolver function. This ensure that these arguments do not show up 38 + // in the stack trace in PHP 7+ only. 39 + $cb = $resolver; 40 + $resolver = $canceller = null; 41 + $this->call($cb); 42 + } 43 + 44 + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface 45 + { 46 + if (null !== $this->result) { 47 + return $this->result->then($onFulfilled, $onRejected); 48 + } 49 + 50 + if (null === $this->canceller) { 51 + return new static($this->resolver($onFulfilled, $onRejected)); 52 + } 53 + 54 + // This promise has a canceller, so we create a new child promise which 55 + // has a canceller that invokes the parent canceller if all other 56 + // followers are also cancelled. We keep a reference to this promise 57 + // instance for the static canceller function and clear this to avoid 58 + // keeping a cyclic reference between parent and follower. 59 + $parent = $this; 60 + ++$parent->requiredCancelRequests; 61 + 62 + return new static( 63 + $this->resolver($onFulfilled, $onRejected), 64 + static function () use (&$parent): void { 65 + assert($parent instanceof self); 66 + --$parent->requiredCancelRequests; 67 + 68 + if ($parent->requiredCancelRequests <= 0) { 69 + $parent->cancel(); 70 + } 71 + 72 + $parent = null; 73 + } 74 + ); 75 + } 76 + 77 + /** 78 + * @template TThrowable of \Throwable 79 + * @template TRejected 80 + * @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected 81 + * @return PromiseInterface<T|TRejected> 82 + */ 83 + public function catch(callable $onRejected): PromiseInterface 84 + { 85 + return $this->then(null, static function (\Throwable $reason) use ($onRejected) { 86 + if (!_checkTypehint($onRejected, $reason)) { 87 + return new RejectedPromise($reason); 88 + } 89 + 90 + /** 91 + * @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected 92 + */ 93 + return $onRejected($reason); 94 + }); 95 + } 96 + 97 + public function finally(callable $onFulfilledOrRejected): PromiseInterface 98 + { 99 + return $this->then(static function ($value) use ($onFulfilledOrRejected): PromiseInterface { 100 + /** @var T $value */ 101 + return resolve($onFulfilledOrRejected())->then(function () use ($value) { 102 + return $value; 103 + }); 104 + }, static function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface { 105 + return resolve($onFulfilledOrRejected())->then(function () use ($reason): RejectedPromise { 106 + return new RejectedPromise($reason); 107 + }); 108 + }); 109 + } 110 + 111 + public function cancel(): void 112 + { 113 + $this->cancelled = true; 114 + $canceller = $this->canceller; 115 + $this->canceller = null; 116 + 117 + $parentCanceller = null; 118 + 119 + if (null !== $this->result) { 120 + // Forward cancellation to rejected promise to avoid reporting unhandled rejection 121 + if ($this->result instanceof RejectedPromise) { 122 + $this->result->cancel(); 123 + } 124 + 125 + // Go up the promise chain and reach the top most promise which is 126 + // itself not following another promise 127 + $root = $this->unwrap($this->result); 128 + 129 + // Return if the root promise is already resolved or a 130 + // FulfilledPromise or RejectedPromise 131 + if (!$root instanceof self || null !== $root->result) { 132 + return; 133 + } 134 + 135 + $root->requiredCancelRequests--; 136 + 137 + if ($root->requiredCancelRequests <= 0) { 138 + $parentCanceller = [$root, 'cancel']; 139 + } 140 + } 141 + 142 + if (null !== $canceller) { 143 + $this->call($canceller); 144 + } 145 + 146 + // For BC, we call the parent canceller after our own canceller 147 + if ($parentCanceller) { 148 + $parentCanceller(); 149 + } 150 + } 151 + 152 + /** 153 + * @deprecated 3.0.0 Use `catch()` instead 154 + * @see self::catch() 155 + */ 156 + public function otherwise(callable $onRejected): PromiseInterface 157 + { 158 + return $this->catch($onRejected); 159 + } 160 + 161 + /** 162 + * @deprecated 3.0.0 Use `finally()` instead 163 + * @see self::finally() 164 + */ 165 + public function always(callable $onFulfilledOrRejected): PromiseInterface 166 + { 167 + return $this->finally($onFulfilledOrRejected); 168 + } 169 + 170 + private function resolver(?callable $onFulfilled = null, ?callable $onRejected = null): callable 171 + { 172 + return function (callable $resolve, callable $reject) use ($onFulfilled, $onRejected): void { 173 + $this->handlers[] = static function (PromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject): void { 174 + $promise = $promise->then($onFulfilled, $onRejected); 175 + 176 + if ($promise instanceof self && $promise->result === null) { 177 + $promise->handlers[] = static function (PromiseInterface $promise) use ($resolve, $reject): void { 178 + $promise->then($resolve, $reject); 179 + }; 180 + } else { 181 + $promise->then($resolve, $reject); 182 + } 183 + }; 184 + }; 185 + } 186 + 187 + private function reject(\Throwable $reason): void 188 + { 189 + if (null !== $this->result) { 190 + return; 191 + } 192 + 193 + $this->settle(reject($reason)); 194 + } 195 + 196 + /** 197 + * @param PromiseInterface<T> $result 198 + */ 199 + private function settle(PromiseInterface $result): void 200 + { 201 + $result = $this->unwrap($result); 202 + 203 + if ($result === $this) { 204 + $result = new RejectedPromise( 205 + new \LogicException('Cannot resolve a promise with itself.') 206 + ); 207 + } 208 + 209 + if ($result instanceof self) { 210 + $result->requiredCancelRequests++; 211 + } else { 212 + // Unset canceller only when not following a pending promise 213 + $this->canceller = null; 214 + } 215 + 216 + $handlers = $this->handlers; 217 + 218 + $this->handlers = []; 219 + $this->result = $result; 220 + 221 + foreach ($handlers as $handler) { 222 + $handler($result); 223 + } 224 + 225 + // Forward cancellation to rejected promise to avoid reporting unhandled rejection 226 + if ($this->cancelled && $result instanceof RejectedPromise) { 227 + $result->cancel(); 228 + } 229 + } 230 + 231 + /** 232 + * @param PromiseInterface<T> $promise 233 + * @return PromiseInterface<T> 234 + */ 235 + private function unwrap(PromiseInterface $promise): PromiseInterface 236 + { 237 + while ($promise instanceof self && null !== $promise->result) { 238 + /** @var PromiseInterface<T> $promise */ 239 + $promise = $promise->result; 240 + } 241 + 242 + return $promise; 243 + } 244 + 245 + /** 246 + * @param callable(callable(mixed):void,callable(\Throwable):void):void $cb 247 + */ 248 + private function call(callable $cb): void 249 + { 250 + // Explicitly overwrite argument with null value. This ensure that this 251 + // argument does not show up in the stack trace in PHP 7+ only. 252 + $callback = $cb; 253 + $cb = null; 254 + 255 + // Use reflection to inspect number of arguments expected by this callback. 256 + // We did some careful benchmarking here: Using reflection to avoid unneeded 257 + // function arguments is actually faster than blindly passing them. 258 + // Also, this helps avoiding unnecessary function arguments in the call stack 259 + // if the callback creates an Exception (creating garbage cycles). 260 + if (\is_array($callback)) { 261 + $ref = new \ReflectionMethod($callback[0], $callback[1]); 262 + } elseif (\is_object($callback) && !$callback instanceof \Closure) { 263 + $ref = new \ReflectionMethod($callback, '__invoke'); 264 + } else { 265 + assert($callback instanceof \Closure || \is_string($callback)); 266 + $ref = new \ReflectionFunction($callback); 267 + } 268 + $args = $ref->getNumberOfParameters(); 269 + 270 + try { 271 + if ($args === 0) { 272 + $callback(); 273 + } else { 274 + // Keep references to this promise instance for the static resolve/reject functions. 275 + // By using static callbacks that are not bound to this instance 276 + // and passing the target promise instance by reference, we can 277 + // still execute its resolving logic and still clear this 278 + // reference when settling the promise. This helps avoiding 279 + // garbage cycles if any callback creates an Exception. 280 + // These assumptions are covered by the test suite, so if you ever feel like 281 + // refactoring this, go ahead, any alternative suggestions are welcome! 282 + $target =& $this; 283 + 284 + $callback( 285 + static function ($value) use (&$target): void { 286 + if ($target !== null) { 287 + $target->settle(resolve($value)); 288 + $target = null; 289 + } 290 + }, 291 + static function (\Throwable $reason) use (&$target): void { 292 + if ($target !== null) { 293 + $target->reject($reason); 294 + $target = null; 295 + } 296 + } 297 + ); 298 + } 299 + } catch (\Throwable $e) { 300 + $target = null; 301 + $this->reject($e); 302 + } 303 + } 304 + }
+152
vendor/react/promise/src/PromiseInterface.php
··· 1 + <?php 2 + 3 + namespace React\Promise; 4 + 5 + /** 6 + * @template-covariant T 7 + */ 8 + interface PromiseInterface 9 + { 10 + /** 11 + * Transforms a promise's value by applying a function to the promise's fulfillment 12 + * or rejection value. Returns a new promise for the transformed result. 13 + * 14 + * The `then()` method registers new fulfilled and rejection handlers with a promise 15 + * (all parameters are optional): 16 + * 17 + * * `$onFulfilled` will be invoked once the promise is fulfilled and passed 18 + * the result as the first argument. 19 + * * `$onRejected` will be invoked once the promise is rejected and passed the 20 + * reason as the first argument. 21 + * 22 + * It returns a new promise that will fulfill with the return value of either 23 + * `$onFulfilled` or `$onRejected`, whichever is called, or will reject with 24 + * the thrown exception if either throws. 25 + * 26 + * A promise makes the following guarantees about handlers registered in 27 + * the same call to `then()`: 28 + * 29 + * 1. Only one of `$onFulfilled` or `$onRejected` will be called, 30 + * never both. 31 + * 2. `$onFulfilled` and `$onRejected` will never be called more 32 + * than once. 33 + * 34 + * @template TFulfilled 35 + * @template TRejected 36 + * @param ?(callable((T is void ? null : T)): (PromiseInterface<TFulfilled>|TFulfilled)) $onFulfilled 37 + * @param ?(callable(\Throwable): (PromiseInterface<TRejected>|TRejected)) $onRejected 38 + * @return PromiseInterface<($onRejected is null ? ($onFulfilled is null ? T : TFulfilled) : ($onFulfilled is null ? T|TRejected : TFulfilled|TRejected))> 39 + */ 40 + public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface; 41 + 42 + /** 43 + * Registers a rejection handler for promise. It is a shortcut for: 44 + * 45 + * ```php 46 + * $promise->then(null, $onRejected); 47 + * ``` 48 + * 49 + * Additionally, you can type hint the `$reason` argument of `$onRejected` to catch 50 + * only specific errors. 51 + * 52 + * @template TThrowable of \Throwable 53 + * @template TRejected 54 + * @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected 55 + * @return PromiseInterface<T|TRejected> 56 + */ 57 + public function catch(callable $onRejected): PromiseInterface; 58 + 59 + /** 60 + * Allows you to execute "cleanup" type tasks in a promise chain. 61 + * 62 + * It arranges for `$onFulfilledOrRejected` to be called, with no arguments, 63 + * when the promise is either fulfilled or rejected. 64 + * 65 + * * If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully, 66 + * `$newPromise` will fulfill with the same value as `$promise`. 67 + * * If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a 68 + * rejected promise, `$newPromise` will reject with the thrown exception or 69 + * rejected promise's reason. 70 + * * If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully, 71 + * `$newPromise` will reject with the same reason as `$promise`. 72 + * * If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a 73 + * rejected promise, `$newPromise` will reject with the thrown exception or 74 + * rejected promise's reason. 75 + * 76 + * `finally()` behaves similarly to the synchronous finally statement. When combined 77 + * with `catch()`, `finally()` allows you to write code that is similar to the familiar 78 + * synchronous catch/finally pair. 79 + * 80 + * Consider the following synchronous code: 81 + * 82 + * ```php 83 + * try { 84 + * return doSomething(); 85 + * } catch(\Exception $e) { 86 + * return handleError($e); 87 + * } finally { 88 + * cleanup(); 89 + * } 90 + * ``` 91 + * 92 + * Similar asynchronous code (with `doSomething()` that returns a promise) can be 93 + * written: 94 + * 95 + * ```php 96 + * return doSomething() 97 + * ->catch('handleError') 98 + * ->finally('cleanup'); 99 + * ``` 100 + * 101 + * @param callable(): (void|PromiseInterface<void>) $onFulfilledOrRejected 102 + * @return PromiseInterface<T> 103 + */ 104 + public function finally(callable $onFulfilledOrRejected): PromiseInterface; 105 + 106 + /** 107 + * The `cancel()` method notifies the creator of the promise that there is no 108 + * further interest in the results of the operation. 109 + * 110 + * Once a promise is settled (either fulfilled or rejected), calling `cancel()` on 111 + * a promise has no effect. 112 + * 113 + * @return void 114 + */ 115 + public function cancel(): void; 116 + 117 + /** 118 + * [Deprecated] Registers a rejection handler for a promise. 119 + * 120 + * This method continues to exist only for BC reasons and to ease upgrading 121 + * between versions. It is an alias for: 122 + * 123 + * ```php 124 + * $promise->catch($onRejected); 125 + * ``` 126 + * 127 + * @template TThrowable of \Throwable 128 + * @template TRejected 129 + * @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected 130 + * @return PromiseInterface<T|TRejected> 131 + * @deprecated 3.0.0 Use catch() instead 132 + * @see self::catch() 133 + */ 134 + public function otherwise(callable $onRejected): PromiseInterface; 135 + 136 + /** 137 + * [Deprecated] Allows you to execute "cleanup" type tasks in a promise chain. 138 + * 139 + * This method continues to exist only for BC reasons and to ease upgrading 140 + * between versions. It is an alias for: 141 + * 142 + * ```php 143 + * $promise->finally($onFulfilledOrRejected); 144 + * ``` 145 + * 146 + * @param callable(): (void|PromiseInterface<void>) $onFulfilledOrRejected 147 + * @return PromiseInterface<T> 148 + * @deprecated 3.0.0 Use finally() instead 149 + * @see self::finally() 150 + */ 151 + public function always(callable $onFulfilledOrRejected): PromiseInterface; 152 + }
+345
vendor/react/promise/src/functions.php
··· 1 + <?php 2 + 3 + namespace React\Promise; 4 + 5 + use React\Promise\Exception\CompositeException; 6 + use React\Promise\Internal\FulfilledPromise; 7 + use React\Promise\Internal\RejectedPromise; 8 + 9 + /** 10 + * Creates a promise for the supplied `$promiseOrValue`. 11 + * 12 + * If `$promiseOrValue` is a value, it will be the resolution value of the 13 + * returned promise. 14 + * 15 + * If `$promiseOrValue` is a thenable (any object that provides a `then()` method), 16 + * a trusted promise that follows the state of the thenable is returned. 17 + * 18 + * If `$promiseOrValue` is a promise, it will be returned as is. 19 + * 20 + * @template T 21 + * @param PromiseInterface<T>|T $promiseOrValue 22 + * @return PromiseInterface<T> 23 + */ 24 + function resolve($promiseOrValue): PromiseInterface 25 + { 26 + if ($promiseOrValue instanceof PromiseInterface) { 27 + return $promiseOrValue; 28 + } 29 + 30 + if (\is_object($promiseOrValue) && \method_exists($promiseOrValue, 'then')) { 31 + $canceller = null; 32 + 33 + if (\method_exists($promiseOrValue, 'cancel')) { 34 + $canceller = [$promiseOrValue, 'cancel']; 35 + assert(\is_callable($canceller)); 36 + } 37 + 38 + /** @var Promise<T> */ 39 + return new Promise(function (callable $resolve, callable $reject) use ($promiseOrValue): void { 40 + $promiseOrValue->then($resolve, $reject); 41 + }, $canceller); 42 + } 43 + 44 + return new FulfilledPromise($promiseOrValue); 45 + } 46 + 47 + /** 48 + * Creates a rejected promise for the supplied `$reason`. 49 + * 50 + * If `$reason` is a value, it will be the rejection value of the 51 + * returned promise. 52 + * 53 + * If `$reason` is a promise, its completion value will be the rejected 54 + * value of the returned promise. 55 + * 56 + * This can be useful in situations where you need to reject a promise without 57 + * throwing an exception. For example, it allows you to propagate a rejection with 58 + * the value of another promise. 59 + * 60 + * @return PromiseInterface<never> 61 + */ 62 + function reject(\Throwable $reason): PromiseInterface 63 + { 64 + return new RejectedPromise($reason); 65 + } 66 + 67 + /** 68 + * Returns a promise that will resolve only once all the items in 69 + * `$promisesOrValues` have resolved. The resolution value of the returned promise 70 + * will be an array containing the resolution values of each of the items in 71 + * `$promisesOrValues`. 72 + * 73 + * @template T 74 + * @param iterable<PromiseInterface<T>|T> $promisesOrValues 75 + * @return PromiseInterface<array<T>> 76 + */ 77 + function all(iterable $promisesOrValues): PromiseInterface 78 + { 79 + $cancellationQueue = new Internal\CancellationQueue(); 80 + 81 + /** @var Promise<array<T>> */ 82 + return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { 83 + $toResolve = 0; 84 + /** @var bool */ 85 + $continue = true; 86 + $values = []; 87 + 88 + foreach ($promisesOrValues as $i => $promiseOrValue) { 89 + $cancellationQueue->enqueue($promiseOrValue); 90 + $values[$i] = null; 91 + ++$toResolve; 92 + 93 + resolve($promiseOrValue)->then( 94 + function ($value) use ($i, &$values, &$toResolve, &$continue, $resolve): void { 95 + $values[$i] = $value; 96 + 97 + if (0 === --$toResolve && !$continue) { 98 + $resolve($values); 99 + } 100 + }, 101 + function (\Throwable $reason) use (&$continue, $reject): void { 102 + $continue = false; 103 + $reject($reason); 104 + } 105 + ); 106 + 107 + if (!$continue && !\is_array($promisesOrValues)) { 108 + break; 109 + } 110 + } 111 + 112 + $continue = false; 113 + if ($toResolve === 0) { 114 + $resolve($values); 115 + } 116 + }, $cancellationQueue); 117 + } 118 + 119 + /** 120 + * Initiates a competitive race that allows one winner. Returns a promise which is 121 + * resolved in the same way the first settled promise resolves. 122 + * 123 + * The returned promise will become **infinitely pending** if `$promisesOrValues` 124 + * contains 0 items. 125 + * 126 + * @template T 127 + * @param iterable<PromiseInterface<T>|T> $promisesOrValues 128 + * @return PromiseInterface<T> 129 + */ 130 + function race(iterable $promisesOrValues): PromiseInterface 131 + { 132 + $cancellationQueue = new Internal\CancellationQueue(); 133 + 134 + /** @var Promise<T> */ 135 + return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { 136 + $continue = true; 137 + 138 + foreach ($promisesOrValues as $promiseOrValue) { 139 + $cancellationQueue->enqueue($promiseOrValue); 140 + 141 + resolve($promiseOrValue)->then($resolve, $reject)->finally(function () use (&$continue): void { 142 + $continue = false; 143 + }); 144 + 145 + if (!$continue && !\is_array($promisesOrValues)) { 146 + break; 147 + } 148 + } 149 + }, $cancellationQueue); 150 + } 151 + 152 + /** 153 + * Returns a promise that will resolve when any one of the items in 154 + * `$promisesOrValues` resolves. The resolution value of the returned promise 155 + * will be the resolution value of the triggering item. 156 + * 157 + * The returned promise will only reject if *all* items in `$promisesOrValues` are 158 + * rejected. The rejection value will be an array of all rejection reasons. 159 + * 160 + * The returned promise will also reject with a `React\Promise\Exception\LengthException` 161 + * if `$promisesOrValues` contains 0 items. 162 + * 163 + * @template T 164 + * @param iterable<PromiseInterface<T>|T> $promisesOrValues 165 + * @return PromiseInterface<T> 166 + */ 167 + function any(iterable $promisesOrValues): PromiseInterface 168 + { 169 + $cancellationQueue = new Internal\CancellationQueue(); 170 + 171 + /** @var Promise<T> */ 172 + return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void { 173 + $toReject = 0; 174 + $continue = true; 175 + $reasons = []; 176 + 177 + foreach ($promisesOrValues as $i => $promiseOrValue) { 178 + $cancellationQueue->enqueue($promiseOrValue); 179 + ++$toReject; 180 + 181 + resolve($promiseOrValue)->then( 182 + function ($value) use ($resolve, &$continue): void { 183 + $continue = false; 184 + $resolve($value); 185 + }, 186 + function (\Throwable $reason) use ($i, &$reasons, &$toReject, $reject, &$continue): void { 187 + $reasons[$i] = $reason; 188 + 189 + if (0 === --$toReject && !$continue) { 190 + $reject(new CompositeException( 191 + $reasons, 192 + 'All promises rejected.' 193 + )); 194 + } 195 + } 196 + ); 197 + 198 + if (!$continue && !\is_array($promisesOrValues)) { 199 + break; 200 + } 201 + } 202 + 203 + $continue = false; 204 + if ($toReject === 0 && !$reasons) { 205 + $reject(new Exception\LengthException( 206 + 'Must contain at least 1 item but contains only 0 items.' 207 + )); 208 + } elseif ($toReject === 0) { 209 + $reject(new CompositeException( 210 + $reasons, 211 + 'All promises rejected.' 212 + )); 213 + } 214 + }, $cancellationQueue); 215 + } 216 + 217 + /** 218 + * Sets the global rejection handler for unhandled promise rejections. 219 + * 220 + * Note that rejected promises should always be handled similar to how any 221 + * exceptions should always be caught in a `try` + `catch` block. If you remove 222 + * the last reference to a rejected promise that has not been handled, it will 223 + * report an unhandled promise rejection. See also the [`reject()` function](#reject) 224 + * for more details. 225 + * 226 + * The `?callable $callback` argument MUST be a valid callback function that 227 + * accepts a single `Throwable` argument or a `null` value to restore the 228 + * default promise rejection handler. The return value of the callback function 229 + * will be ignored and has no effect, so you SHOULD return a `void` value. The 230 + * callback function MUST NOT throw or the program will be terminated with a 231 + * fatal error. 232 + * 233 + * The function returns the previous rejection handler or `null` if using the 234 + * default promise rejection handler. 235 + * 236 + * The default promise rejection handler will log an error message plus its 237 + * stack trace: 238 + * 239 + * ```php 240 + * // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2 241 + * React\Promise\reject(new RuntimeException('Unhandled')); 242 + * ``` 243 + * 244 + * The promise rejection handler may be used to use customize the log message or 245 + * write to custom log targets. As a rule of thumb, this function should only be 246 + * used as a last resort and promise rejections are best handled with either the 247 + * [`then()` method](#promiseinterfacethen), the 248 + * [`catch()` method](#promiseinterfacecatch), or the 249 + * [`finally()` method](#promiseinterfacefinally). 250 + * See also the [`reject()` function](#reject) for more details. 251 + * 252 + * @param callable(\Throwable):void|null $callback 253 + * @return callable(\Throwable):void|null 254 + */ 255 + function set_rejection_handler(?callable $callback): ?callable 256 + { 257 + static $current = null; 258 + $previous = $current; 259 + $current = $callback; 260 + 261 + return $previous; 262 + } 263 + 264 + /** 265 + * @internal 266 + */ 267 + function _checkTypehint(callable $callback, \Throwable $reason): bool 268 + { 269 + if (\is_array($callback)) { 270 + $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]); 271 + } elseif (\is_object($callback) && !$callback instanceof \Closure) { 272 + $callbackReflection = new \ReflectionMethod($callback, '__invoke'); 273 + } else { 274 + assert($callback instanceof \Closure || \is_string($callback)); 275 + $callbackReflection = new \ReflectionFunction($callback); 276 + } 277 + 278 + $parameters = $callbackReflection->getParameters(); 279 + 280 + if (!isset($parameters[0])) { 281 + return true; 282 + } 283 + 284 + $expectedException = $parameters[0]; 285 + 286 + // Extract the type of the argument and handle different possibilities 287 + $type = $expectedException->getType(); 288 + 289 + $isTypeUnion = true; 290 + $types = []; 291 + 292 + switch (true) { 293 + case $type === null: 294 + break; 295 + case $type instanceof \ReflectionNamedType: 296 + $types = [$type]; 297 + break; 298 + case $type instanceof \ReflectionIntersectionType: 299 + $isTypeUnion = false; 300 + case $type instanceof \ReflectionUnionType: 301 + $types = $type->getTypes(); 302 + break; 303 + default: 304 + throw new \LogicException('Unexpected return value of ReflectionParameter::getType'); 305 + } 306 + 307 + // If there is no type restriction, it matches 308 + if (empty($types)) { 309 + return true; 310 + } 311 + 312 + foreach ($types as $type) { 313 + 314 + if ($type instanceof \ReflectionIntersectionType) { 315 + foreach ($type->getTypes() as $typeToMatch) { 316 + assert($typeToMatch instanceof \ReflectionNamedType); 317 + $name = $typeToMatch->getName(); 318 + if (!($matches = (!$typeToMatch->isBuiltin() && $reason instanceof $name))) { 319 + break; 320 + } 321 + } 322 + assert(isset($matches)); 323 + } else { 324 + assert($type instanceof \ReflectionNamedType); 325 + $name = $type->getName(); 326 + $matches = !$type->isBuiltin() && $reason instanceof $name; 327 + } 328 + 329 + // If we look for a single match (union), we can return early on match 330 + // If we look for a full match (intersection), we can return early on mismatch 331 + if ($matches) { 332 + if ($isTypeUnion) { 333 + return true; 334 + } 335 + } else { 336 + if (!$isTypeUnion) { 337 + return false; 338 + } 339 + } 340 + } 341 + 342 + // If we look for a single match (union) and did not return early, we matched no type and are false 343 + // If we look for a full match (intersection) and did not return early, we matched all types and are true 344 + return $isTypeUnion ? false : true; 345 + }
+5
vendor/react/promise/src/functions_include.php
··· 1 + <?php 2 + 3 + if (!\function_exists('React\Promise\resolve')) { 4 + require __DIR__.'/functions.php'; 5 + }