PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 27.7
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v27.7
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / vendor_prefixed / guzzlehttp / promises / src / Promise.php

Promise.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 27.7, at vendor_prefixed/guzzlehttp/promises/src/Promise.php

237 lines 9.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare (strict_types=1);
4 namespace YoastSEO_Vendor\GuzzleHttp\Promise;
5
6 /**
7 * Promises/A+ implementation that avoids recursion when possible.
8 *
9 * @see https://promisesaplus.com/
10 *
11 * @final
12 */
13 class Promise implements \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
14 {
15 private $state = self::PENDING;
16 private $result;
17 private $cancelFn;
18 private $waitFn;
19 private $waitList;
20 private $handlers = [];
21 /**
22 * @param callable $waitFn Fn that when invoked resolves the promise.
23 * @param callable $cancelFn Fn that when invoked cancels the promise.
24 */
25 public function __construct(?callable $waitFn = null, ?callable $cancelFn = null)
26 {
27 $this->waitFn = $waitFn;
28 $this->cancelFn = $cancelFn;
29 }
30 public function then(?callable $onFulfilled = null, ?callable $onRejected = null) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
31 {
32 if ($this->state === self::PENDING) {
33 $p = new \YoastSEO_Vendor\GuzzleHttp\Promise\Promise(null, [$this, 'cancel']);
34 $this->handlers[] = [$p, $onFulfilled, $onRejected];
35 $p->waitList = $this->waitList;
36 $p->waitList[] = $this;
37 return $p;
38 }
39 // Return a fulfilled promise and immediately invoke any callbacks.
40 if ($this->state === self::FULFILLED) {
41 $promise = \YoastSEO_Vendor\GuzzleHttp\Promise\Create::promiseFor($this->result);
42 return $onFulfilled ? $promise->then($onFulfilled) : $promise;
43 }
44 // It's either cancelled or rejected, so return a rejected promise
45 // and immediately invoke any callbacks.
46 $rejection = \YoastSEO_Vendor\GuzzleHttp\Promise\Create::rejectionFor($this->result);
47 return $onRejected ? $rejection->then(null, $onRejected) : $rejection;
48 }
49 public function otherwise(callable $onRejected) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
50 {
51 return $this->then(null, $onRejected);
52 }
53 public function wait(bool $unwrap = \true)
54 {
55 $this->waitIfPending();
56 if ($this->result instanceof \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface) {
57 return $this->result->wait($unwrap);
58 }
59 if ($unwrap) {
60 if ($this->state === self::FULFILLED) {
61 return $this->result;
62 }
63 // It's rejected so "unwrap" and throw an exception.
64 throw \YoastSEO_Vendor\GuzzleHttp\Promise\Create::exceptionFor($this->result);
65 }
66 }
67 public function getState() : string
68 {
69 return $this->state;
70 }
71 public function cancel() : void
72 {
73 if ($this->state !== self::PENDING) {
74 return;
75 }
76 $this->waitFn = $this->waitList = null;
77 if ($this->cancelFn) {
78 $fn = $this->cancelFn;
79 $this->cancelFn = null;
80 try {
81 $fn();
82 } catch (\Throwable $e) {
83 $this->reject($e);
84 }
85 }
86 // Reject the promise only if it wasn't rejected in a then callback.
87 /** @psalm-suppress RedundantCondition */
88 if ($this->state === self::PENDING) {
89 $this->reject(new \YoastSEO_Vendor\GuzzleHttp\Promise\CancellationException('Promise has been cancelled'));
90 }
91 }
92 public function resolve($value) : void
93 {
94 $this->settle(self::FULFILLED, $value);
95 }
96 public function reject($reason) : void
97 {
98 $this->settle(self::REJECTED, $reason);
99 }
100 private function settle(string $state, $value) : void
101 {
102 if ($this->state !== self::PENDING) {
103 // Ignore calls with the same resolution.
104 if ($state === $this->state && $value === $this->result) {
105 return;
106 }
107 throw $this->state === $state ? new \LogicException("The promise is already {$state}.") : new \LogicException("Cannot change a {$this->state} promise to {$state}");
108 }
109 if ($value === $this) {
110 throw new \LogicException('Cannot fulfill or reject a promise with itself');
111 }
112 // Clear out the state of the promise but stash the handlers.
113 $this->state = $state;
114 $this->result = $value;
115 $handlers = $this->handlers;
116 $this->handlers = null;
117 $this->waitList = $this->waitFn = null;
118 $this->cancelFn = null;
119 if (!$handlers) {
120 return;
121 }
122 // If the value was not a settled promise or a thenable, then resolve
123 // it in the task queue using the correct ID.
124 if (!\is_object($value) || !\method_exists($value, 'then')) {
125 $id = $state === self::FULFILLED ? 1 : 2;
126 // It's a success, so resolve the handlers in the queue.
127 \YoastSEO_Vendor\GuzzleHttp\Promise\Utils::queue()->add(static function () use($id, $value, $handlers) : void {
128 foreach ($handlers as $handler) {
129 self::callHandler($id, $value, $handler);
130 }
131 });
132 } elseif ($value instanceof \YoastSEO_Vendor\GuzzleHttp\Promise\Promise && \YoastSEO_Vendor\GuzzleHttp\Promise\Is::pending($value)) {
133 // We can just merge our handlers onto the next promise.
134 $value->handlers = \array_merge($value->handlers, $handlers);
135 } else {
136 // Resolve the handlers when the forwarded promise is resolved.
137 $value->then(static function ($value) use($handlers) : void {
138 foreach ($handlers as $handler) {
139 self::callHandler(1, $value, $handler);
140 }
141 }, static function ($reason) use($handlers) : void {
142 foreach ($handlers as $handler) {
143 self::callHandler(2, $reason, $handler);
144 }
145 });
146 }
147 }
148 /**
149 * Call a stack of handlers using a specific callback index and value.
150 *
151 * @param int $index 1 (resolve) or 2 (reject).
152 * @param mixed $value Value to pass to the callback.
153 * @param array $handler Array of handler data (promise and callbacks).
154 */
155 private static function callHandler(int $index, $value, array $handler) : void
156 {
157 /** @var PromiseInterface $promise */
158 $promise = $handler[0];
159 // The promise may have been cancelled or resolved before placing
160 // this thunk in the queue.
161 if (\YoastSEO_Vendor\GuzzleHttp\Promise\Is::settled($promise)) {
162 return;
163 }
164 try {
165 if (isset($handler[$index])) {
166 /*
167 * If $f throws an exception, then $handler will be in the exception
168 * stack trace. Since $handler contains a reference to the callable
169 * itself we get a circular reference. We clear the $handler
170 * here to avoid that memory leak.
171 */
172 $f = $handler[$index];
173 unset($handler);
174 $promise->resolve($f($value));
175 } elseif ($index === 1) {
176 // Forward resolution values as-is.
177 $promise->resolve($value);
178 } else {
179 // Forward rejections down the chain.
180 $promise->reject($value);
181 }
182 } catch (\Throwable $reason) {
183 $promise->reject($reason);
184 }
185 }
186 private function waitIfPending() : void
187 {
188 if ($this->state !== self::PENDING) {
189 return;
190 } elseif ($this->waitFn) {
191 $this->invokeWaitFn();
192 } elseif ($this->waitList) {
193 $this->invokeWaitList();
194 } else {
195 // If there's no wait function, then reject the promise.
196 $this->reject('Cannot wait on a promise that has ' . 'no internal wait function. You must provide a wait ' . 'function when constructing the promise to be able to ' . 'wait on a promise.');
197 }
198 \YoastSEO_Vendor\GuzzleHttp\Promise\Utils::queue()->run();
199 /** @psalm-suppress RedundantCondition */
200 if ($this->state === self::PENDING) {
201 $this->reject('Invoking the wait callback did not resolve the promise');
202 }
203 }
204 private function invokeWaitFn() : void
205 {
206 try {
207 $wfn = $this->waitFn;
208 $this->waitFn = null;
209 $wfn(\true);
210 } catch (\Throwable $reason) {
211 if ($this->state === self::PENDING) {
212 // The promise has not been resolved yet, so reject the promise
213 // with the exception.
214 $this->reject($reason);
215 } else {
216 // The promise was already resolved, so there's a problem in
217 // the application.
218 throw $reason;
219 }
220 }
221 }
222 private function invokeWaitList() : void
223 {
224 $waitList = $this->waitList;
225 $this->waitList = null;
226 foreach ($waitList as $result) {
227 do {
228 $result->waitIfPending();
229 $result = $result->result;
230 } while ($result instanceof \YoastSEO_Vendor\GuzzleHttp\Promise\Promise);
231 if ($result instanceof \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface) {
232 $result->wait(\false);
233 }
234 }
235 }
236 }
237