Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.95% covered (success)
95.95%
71 / 74
83.33% covered (success)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Promise
95.95% covered (success)
95.95%
71 / 74
83.33% covered (success)
83.33%
5 / 6
34
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 wait
88.46% covered (success)
88.46%
23 / 26
0.00% covered (danger)
0.00%
0 / 1
11.19
 resolveResponse
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 resolve
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
1 / 1
14
 cancel
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
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;
16
17use Pop\Http\Client\Handler\CurlMulti;
18use Pop\Http\Client\Response;
19use Pop\Http\Promise\Exception;
20use ReflectionException;
21
22/**
23 * 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 */
32class Promise extends Promise\AbstractPromise
33{
34
35    /**
36     * Constructor
37     *
38     * Instantiate the Promise object
39     *
40     * @param  Client|CurlMulti $promiser
41     */
42    public function __construct(Client|CurlMulti $promiser)
43    {
44        $this->setPromiser($promiser);
45    }
46
47    /**
48     * Factory to create a Promise object
49     *
50     * @param  Client|CurlMulti $promiser
51     * @return static
52     */
53    public static function create(Client|CurlMulti $promiser): static
54    {
55        return new static($promiser);
56    }
57
58    /**
59     * Wait method
60     *
61     * @param  bool $unwrap
62     * @throws Exception|Promise\Exception|ReflectionException|Client\Exception|\Pop\Utils\Exception|\Pop\Http\Exception
63     * @return Response|string|array|null
64     */
65    public function wait(bool $unwrap = true): Response|string|array|null
66    {
67        $multi = ($this->promiser instanceof CurlMulti);
68        $auto  = (!($multi) && ($this->promiser->hasOption('auto')) &&
69            ($this->promiser->getOption('auto')));
70
71        if (($this->isFulfilled()) && ($this->promiser->isComplete())) {
72            return $this->resolveResponse($multi, $auto);
73        }
74
75        $this->setState(self::PENDING);
76
77        if ($multi) {
78            $running = null;
79            do {
80                $this->promiser->send($running);
81            } while ($running);
82        } else {
83            $this->promiser->dispatch();
84        }
85
86        if ($this->promiser->isComplete()) {
87            if ($this->promiser->isError()) {
88                $this->setState(self::REJECTED);
89                if ($unwrap) {
90                    if ($multi) {
91                        throw new Exception('Error: There was an error with one of the multiple requests.');
92                    } else {
93                        throw new Exception(
94                            'Error: ' . $this->promiser->getResponse()->getCode() . ' ' .
95                            $this->promiser->getResponse()->getMessage()
96                        );
97                    }
98                }
99            } else {
100                $this->setState(self::FULFILLED);
101                return $this->resolveResponse($multi, $auto);
102            }
103        } else if ($unwrap) {
104            throw new Exception('Error: Unable to complete request.');
105        }
106
107        return null;
108    }
109
110    /**
111     * Resolve the promiser's current response(s) - the same 3-way resolution
112     * (all responses for a multi promiser, auto-parsed vs raw Response otherwise)
113     * used by every response-fetching call site in wait()/resolve()
114     *
115     * @param  bool $multi
116     * @param  bool $auto
117     * @return Response|string|array|null
118     */
119    protected function resolveResponse(bool $multi, bool $auto): Response|string|array|null
120    {
121        if ($multi) {
122            return $this->promiser->getAllResponses();
123        }
124
125        return (($auto) && ($this->promiser->hasResponse())) ?
126            $this->promiser->getResponse()->getParsedResponse() : $this->promiser->getResponse();
127    }
128
129    /**
130     * Resolve method
131     *
132     * @throws Client\Exception|Exception|ReflectionException|\Pop\Utils\Exception|\Pop\Http\Exception
133     * @return void
134     */
135    public function resolve(): void
136    {
137        if ($this->getState() !== self::PENDING) {
138            return;
139        }
140
141        $multi = ($this->promiser instanceof CurlMulti);
142        $auto  = (!($multi) && ($this->promiser->hasOption('auto')) &&
143            ($this->promiser->getOption('auto')));
144
145        if ($multi) {
146            $running = null;
147            do {
148                $this->promiser->send($running);
149            } while ($running);
150        } else {
151            $this->promiser->dispatch();
152        }
153
154        if ($this->promiser->isComplete()) {
155            if ($this->promiser->isSuccess()) {
156                if (!$this->hasSuccess()) {
157                    throw new Exception('Error: The success callback has not been set.');
158                }
159
160                $result = null;
161                foreach ($this->success as $i => $success) {
162                    // Forward success callbacks to next promise
163                    if ($result instanceof Promise) {
164                        $result = $this->forward($result, $i);
165                        break;
166                    // Else, execute callback
167                    } else {
168                        $response = $this->resolveResponse($multi, $auto);
169                        $result = $success->call([
170                            'response' => $response
171                        ]);
172                    }
173                }
174
175                $this->setState(self::FULFILLED);
176
177                if ($result instanceof Promise) {
178                    $result->resolve();
179                }
180            } else if ($this->promiser->isError()) {
181                if (!$this->hasFailure()) {
182                    throw new Exception('Error: The failure callback has not been set.');
183                }
184                $this->setState(self::REJECTED);
185                $response = $this->resolveResponse($multi, $auto);
186                $this->failure->call([
187                    'response' => $response
188                ]);
189            }
190        }
191
192        if ($this->hasFinally()) {
193            $this->finally->call(['promise' => $this]);
194        }
195    }
196
197    /**
198     * Cancel method
199     *
200     * @throws Exception|ReflectionException|\Pop\Utils\Exception
201     * @return void
202     */
203    public function cancel(): void
204    {
205        if ($this->getState() !== self::PENDING) {
206            return;
207        }
208        if (!$this->hasCancel()) {
209            throw new Exception('Error: The cancel callback has not been set.');
210        }
211        $this->setState(self::CANCELLED);
212        $this->cancel->call(['promise' => $this]);
213    }
214
215}