friendship ended with social-app. php is my new best friend
1<?php
2
3namespace React\Promise;
4
5use React\Promise\Exception\CompositeException;
6use React\Promise\Internal\FulfilledPromise;
7use 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 */
24function 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 */
62function 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 */
77function 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 */
130function 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 */
167function 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 */
255function 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 */
267function _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}