Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
69 / 69
100.00% covered (success)
100.00%
26 / 26
CRAP
100.00% covered (success)
100.00%
1 / 1
AbstractPromise
100.00% covered (success)
100.00%
69 / 69
100.00% covered (success)
100.00%
26 / 26
50
100.00% covered (success)
100.00%
1 / 1
 setPromiser
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getPromiser
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasPromiser
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSuccess
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getSuccess
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 hasSuccess
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setFailure
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getFailure
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasFailure
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setCancel
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getCancel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasCancel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setFinally
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getFinally
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasFinally
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setState
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
5
 getState
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasState
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isPending
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isFulfilled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isRejected
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isCancelled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 then
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 catch
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 finally
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 forward
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 wait
n/a
0 / 0
n/a
0 / 0
0
 resolve
n/a
0 / 0
n/a
0 / 0
0
 cancel
n/a
0 / 0
n/a
0 / 0
0
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Http\Promise;
16
17use Pop\Http\Client;
18use Pop\Http\Client\Response;
19use Pop\Http\Client\Handler\CurlMulti;
20use Pop\Utils\CallableObject;
21
22/**
23 * Abstract HTTP promise class
24 *
25 * @category   Pop
26 * @package    Pop\Http
27 * @author     Nick Sagona, III <nick@popphp.org>
28 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
29 * @license    https://www.popphp.org/license     New BSD License
30 * @version    6.0.0
31 */
32abstract class AbstractPromise implements PromiseInterface
33{
34
35    /**
36     * Status constants
37     * @var string
38     */
39    const PENDING   = 'PENDING';
40    const FULFILLED = 'FULFILLED';
41    const REJECTED  = 'REJECTED';
42    const CANCELLED = 'CANCELLED';
43
44    /**
45     * Client Promiser
46     * @var Client|CurlMulti|null
47     */
48    protected Client|CurlMulti|null $promiser = null;
49
50    /**
51     * Success callables
52     * @var array
53     */
54    protected array $success = [];
55
56    /**
57     * Failure callable
58     * @var ?CallableObject
59     */
60    protected ?CallableObject $failure = null;
61
62    /**
63     * Cancel callable
64     * @var ?CallableObject
65     */
66    protected ?CallableObject $cancel = null;
67
68    /**
69     * Cancel callable
70     * @var ?CallableObject
71     */
72    protected ?CallableObject $finally = null;
73
74    /**
75     * Current state
76     * @var string
77     */
78    protected string $state = self::PENDING;
79
80    /**
81     * Method to set client promiser
82     *
83     * @param  Client|CurlMulti $promiser
84     * @return AbstractPromise
85     */
86    public function setPromiser(Client|CurlMulti $promiser): AbstractPromise
87    {
88        $this->promiser = $promiser;
89        return $this;
90    }
91
92    /**
93     * Method to get client promiser
94     *
95     * @return Client|CurlMulti
96     */
97    public function getPromiser(): Client|CurlMulti
98    {
99        return $this->promiser;
100    }
101
102    /**
103     * Method to check client promiser
104     *
105     * @return bool
106     */
107    public function hasPromiser(): bool
108    {
109        return ($this->promiser !== null);
110    }
111
112    /**
113     * Method to set success callable
114     *
115     * @param  mixed $success
116     * @throws Exception
117     * @return AbstractPromise
118     */
119    public function setSuccess(mixed $success): AbstractPromise
120    {
121        if (!($success instanceof CallableObject) && !is_callable($success)) {
122            throw new Exception('Error: The success callback must be an instance of CallableObject or a callable');
123        }
124        if (!($success instanceof CallableObject)) {
125            $success = new CallableObject($success);
126        }
127
128        $this->success[] = $success;
129        return $this;
130    }
131
132    /**
133     * Method to get success callable
134     *
135     * @param  ?int $i
136     * @return array|CallableObject|null
137     */
138    public function getSuccess(?int $i = null): array|CallableObject|null
139    {
140        if ($i !== null) {
141            return $this->success[$i] ?? null;
142        } else {
143            return $this->success;
144        }
145    }
146
147    /**
148     * Method to check success callable
149     *
150     * @param  ?int $i
151     * @return bool
152     */
153    public function hasSuccess(?int $i = null): bool
154    {
155        if ($i !== null) {
156            return (isset($this->success[$i]));
157        } else {
158            return (!empty($this->success));
159        }
160    }
161
162    /**
163     * Method to set failure callable
164     *
165     * @param  mixed $failure
166     * @return AbstractPromise
167     */
168    public function setFailure(mixed $failure): AbstractPromise
169    {
170        if (!($failure instanceof CallableObject) && !is_callable($failure)) {
171            throw new Exception('Error: The failure callback must be an instance of CallableObject or a callable');
172        }
173        if (!($failure instanceof CallableObject)) {
174            $failure = new CallableObject($failure);
175        }
176
177        $this->failure = $failure;
178        return $this;
179    }
180
181    /**
182     * Method to get failure callable
183     *
184     * @return CallableObject|null
185     */
186    public function getFailure(): CallableObject|null
187    {
188        return $this->failure;
189    }
190
191    /**
192     * Method to check failure callable
193     *
194     * @return bool
195     */
196    public function hasFailure(): bool
197    {
198        return ($this->failure !== null);
199    }
200
201    /**
202     * Method to set cancel callable
203     *
204     * @param  mixed $cancel
205     * @return AbstractPromise
206     */
207    public function setCancel(mixed $cancel): AbstractPromise
208    {
209        if (!($cancel instanceof CallableObject) && !is_callable($cancel)) {
210            throw new Exception('Error: The cancel callback must be an instance of CallableObject or a callable');
211        }
212        if (!($cancel instanceof CallableObject)) {
213            $cancel = new CallableObject($cancel);
214        }
215
216        $this->cancel = $cancel;
217        return $this;
218    }
219
220    /**
221     * Method to get cancel callable
222     *
223     * @return CallableObject|null
224     */
225    public function getCancel(): CallableObject|null
226    {
227        return $this->cancel;
228    }
229
230    /**
231     * Method to check cancel callable
232     *
233     * @return bool
234     */
235    public function hasCancel(): bool
236    {
237        return ($this->cancel !== null);
238    }
239
240    /**
241     * Method to set finally callable
242     *
243     * @param  mixed $finally
244     * @return AbstractPromise
245     */
246    public function setFinally(mixed $finally): AbstractPromise
247    {
248        if (!($finally instanceof CallableObject) && !is_callable($finally)) {
249            throw new Exception('Error: The cancel callback must be an instance of CallableObject or a callable');
250        }
251        if (!($finally instanceof CallableObject)) {
252            $finally = new CallableObject($finally);
253        }
254
255        $this->finally = $finally;
256        return $this;
257    }
258
259    /**
260     * Method to get finally callable
261     *
262     * @return CallableObject|null
263     */
264    public function getFinally(): CallableObject|null
265    {
266        return $this->finally;
267    }
268
269    /**
270     * Method to check finally callable
271     *
272     * @return bool
273     */
274    public function hasFinally(): bool
275    {
276        return ($this->finally !== null);
277    }
278
279    /**
280     * Method to set current state
281     *
282     * @param  string $state
283     * @throws Exception
284     * @return AbstractPromise
285     */
286    public function setState(string $state): AbstractPromise
287    {
288        if (($state !== static::PENDING) && ($state !== static::FULFILLED) && ($state !== static::REJECTED) && ($state !== static::CANCELLED)) {
289            throw new Exception('Error: That state is not allowed.');
290        }
291        $this->state = $state;
292        return $this;
293    }
294
295    /**
296     * Method to get current state
297     *
298     * @return string
299     */
300    public function getState(): string
301    {
302        return $this->state;
303    }
304
305    /**
306     * Method to check current state
307     *
308     * @return bool
309     */
310    public function hasState(): bool
311    {
312        return ($this->state !== '');
313    }
314
315    /**
316     * Determine is the promise is pending
317     *
318     * @return bool
319     */
320    public function isPending(): bool
321    {
322        return ($this->state == static::PENDING);
323    }
324
325    /**
326     * Determine is the promise is fulfilled
327     *
328     * @return bool
329     */
330    public function isFulfilled(): bool
331    {
332        return ($this->state == static::FULFILLED);
333    }
334
335    /**
336     * Determine is the promise is rejected
337     *
338     * @return bool
339     */
340    public function isRejected(): bool
341    {
342        return ($this->state == static::REJECTED);
343    }
344
345    /**
346     * Determine is the promise is cancelled
347     *
348     * @return bool
349     */
350    public function isCancelled(): bool
351    {
352        return ($this->state == static::CANCELLED);
353    }
354
355    /**
356     * Then method
357     *
358     * @param  mixed $success
359     * @param  bool  $resolve
360     * @return AbstractPromise
361     */
362    public function then(mixed $success, bool $resolve = false): AbstractPromise
363    {
364        $this->setSuccess($success);
365
366        if ($resolve) {
367            $this->resolve();
368        }
369
370        return $this;
371    }
372
373    /**
374     * Method to set failure callable (alias)
375     *
376     * @param  mixed $failure
377     * @param  bool $resolve
378     * @return AbstractPromise
379     */
380    public function catch(mixed $failure, bool $resolve = false): AbstractPromise
381    {
382        $this->setFailure($failure);
383
384        if ($resolve) {
385            $this->resolve();
386        }
387
388        return $this;
389    }
390
391    /**
392     * Method to set finally callable (alias)
393     *
394     * @param  mixed $finally
395     * @return AbstractPromise
396     */
397    public function finally(mixed $finally, bool $resolve = false): AbstractPromise
398    {
399        $this->setFinally($finally);
400
401        if ($resolve) {
402            $this->resolve();
403        }
404
405        return $this;
406    }
407
408    /**
409     * Forward method
410     *
411     * @param  PromiseInterface $nextPromise
412     * @param  int              $i
413     * @return PromiseInterface
414     */
415    public function forward(PromiseInterface $nextPromise, int $i = 0): PromiseInterface
416    {
417        for ($j = $i; $j < count($this->success); $j++) {
418            $nextPromise->then($this->success[$j]);
419        }
420        if ($this->hasFailure()) {
421            $nextPromise->setFailure($this->failure);
422        }
423        if ($this->hasCancel()) {
424            $nextPromise->setCancel($this->cancel);
425        }
426
427        return $nextPromise;
428    }
429
430    /**
431     * Wait method
432     *
433     * @param  bool $unwrap
434     * @return Response|string|array|null
435     */
436    abstract public function wait(bool $unwrap = true): Response|string|array|null;
437
438    /**
439     * Resolve method
440     *
441     * @return void
442     */
443    abstract public function resolve(): void;
444
445    /**
446     * Cancel method
447     *
448     * @return void
449     */
450    abstract public function cancel(): void;
451
452}