PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.9
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.9
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 / EachPromise.php

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

209 lines 7.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace YoastSEO_Vendor\GuzzleHttp\Promise;
4
5 /**
6 * Represents a promise that iterates over many promises and invokes
7 * side-effect functions in the process.
8 */
9 class EachPromise implements \YoastSEO_Vendor\GuzzleHttp\Promise\PromisorInterface
10 {
11 private $pending = [];
12 private $nextPendingIndex = 0;
13 /** @var \Iterator|null */
14 private $iterable;
15 /** @var callable|int|null */
16 private $concurrency;
17 /** @var callable|null */
18 private $onFulfilled;
19 /** @var callable|null */
20 private $onRejected;
21 /** @var Promise|null */
22 private $aggregate;
23 /** @var bool|null */
24 private $mutex;
25 /**
26 * Configuration hash can include the following key value pairs:
27 *
28 * - fulfilled: (callable) Invoked when a promise fulfills. The function
29 * is invoked with three arguments: the fulfillment value, the index
30 * position from the iterable list of the promise, and the aggregate
31 * promise that manages all of the promises. The aggregate promise may
32 * be resolved from within the callback to short-circuit the promise.
33 * - rejected: (callable) Invoked when a promise is rejected. The
34 * function is invoked with three arguments: the rejection reason, the
35 * index position from the iterable list of the promise, and the
36 * aggregate promise that manages all of the promises. The aggregate
37 * promise may be resolved from within the callback to short-circuit
38 * the promise.
39 * - concurrency: (integer) Pass this configuration option to limit the
40 * allowed number of outstanding concurrently executing promises,
41 * creating a capped pool of promises. There is no limit by default.
42 *
43 * @param mixed $iterable Promises or values to iterate.
44 * @param array $config Configuration options
45 */
46 public function __construct($iterable, array $config = [])
47 {
48 $this->iterable = \YoastSEO_Vendor\GuzzleHttp\Promise\Create::iterFor($iterable);
49 if (isset($config['concurrency'])) {
50 $this->concurrency = $config['concurrency'];
51 }
52 if (isset($config['fulfilled'])) {
53 $this->onFulfilled = $config['fulfilled'];
54 }
55 if (isset($config['rejected'])) {
56 $this->onRejected = $config['rejected'];
57 }
58 }
59 /** @psalm-suppress InvalidNullableReturnType */
60 public function promise()
61 {
62 if ($this->aggregate) {
63 return $this->aggregate;
64 }
65 try {
66 $this->createPromise();
67 /** @psalm-assert Promise $this->aggregate */
68 $this->iterable->rewind();
69 $this->refillPending();
70 } catch (\Throwable $e) {
71 /**
72 * @psalm-suppress NullReference
73 * @phpstan-ignore-next-line
74 */
75 $this->aggregate->reject($e);
76 } catch (\Exception $e) {
77 /**
78 * @psalm-suppress NullReference
79 * @phpstan-ignore-next-line
80 */
81 $this->aggregate->reject($e);
82 }
83 /**
84 * @psalm-suppress NullableReturnStatement
85 * @phpstan-ignore-next-line
86 */
87 return $this->aggregate;
88 }
89 private function createPromise()
90 {
91 $this->mutex = \false;
92 $this->aggregate = new \YoastSEO_Vendor\GuzzleHttp\Promise\Promise(function () {
93 if ($this->checkIfFinished()) {
94 return;
95 }
96 \reset($this->pending);
97 // Consume a potentially fluctuating list of promises while
98 // ensuring that indexes are maintained (precluding array_shift).
99 while ($promise = \current($this->pending)) {
100 \next($this->pending);
101 $promise->wait();
102 if (\YoastSEO_Vendor\GuzzleHttp\Promise\Is::settled($this->aggregate)) {
103 return;
104 }
105 }
106 });
107 // Clear the references when the promise is resolved.
108 $clearFn = function () {
109 $this->iterable = $this->concurrency = $this->pending = null;
110 $this->onFulfilled = $this->onRejected = null;
111 $this->nextPendingIndex = 0;
112 };
113 $this->aggregate->then($clearFn, $clearFn);
114 }
115 private function refillPending()
116 {
117 if (!$this->concurrency) {
118 // Add all pending promises.
119 while ($this->addPending() && $this->advanceIterator()) {
120 }
121 return;
122 }
123 // Add only up to N pending promises.
124 $concurrency = \is_callable($this->concurrency) ? \call_user_func($this->concurrency, \count($this->pending)) : $this->concurrency;
125 $concurrency = \max($concurrency - \count($this->pending), 0);
126 // Concurrency may be set to 0 to disallow new promises.
127 if (!$concurrency) {
128 return;
129 }
130 // Add the first pending promise.
131 $this->addPending();
132 // Note this is special handling for concurrency=1 so that we do
133 // not advance the iterator after adding the first promise. This
134 // helps work around issues with generators that might not have the
135 // next value to yield until promise callbacks are called.
136 while (--$concurrency && $this->advanceIterator() && $this->addPending()) {
137 }
138 }
139 private function addPending()
140 {
141 if (!$this->iterable || !$this->iterable->valid()) {
142 return \false;
143 }
144 $promise = \YoastSEO_Vendor\GuzzleHttp\Promise\Create::promiseFor($this->iterable->current());
145 $key = $this->iterable->key();
146 // Iterable keys may not be unique, so we use a counter to
147 // guarantee uniqueness
148 $idx = $this->nextPendingIndex++;
149 $this->pending[$idx] = $promise->then(function ($value) use($idx, $key) {
150 if ($this->onFulfilled) {
151 \call_user_func($this->onFulfilled, $value, $key, $this->aggregate);
152 }
153 $this->step($idx);
154 }, function ($reason) use($idx, $key) {
155 if ($this->onRejected) {
156 \call_user_func($this->onRejected, $reason, $key, $this->aggregate);
157 }
158 $this->step($idx);
159 });
160 return \true;
161 }
162 private function advanceIterator()
163 {
164 // Place a lock on the iterator so that we ensure to not recurse,
165 // preventing fatal generator errors.
166 if ($this->mutex) {
167 return \false;
168 }
169 $this->mutex = \true;
170 try {
171 $this->iterable->next();
172 $this->mutex = \false;
173 return \true;
174 } catch (\Throwable $e) {
175 $this->aggregate->reject($e);
176 $this->mutex = \false;
177 return \false;
178 } catch (\Exception $e) {
179 $this->aggregate->reject($e);
180 $this->mutex = \false;
181 return \false;
182 }
183 }
184 private function step($idx)
185 {
186 // If the promise was already resolved, then ignore this step.
187 if (\YoastSEO_Vendor\GuzzleHttp\Promise\Is::settled($this->aggregate)) {
188 return;
189 }
190 unset($this->pending[$idx]);
191 // Only refill pending promises if we are not locked, preventing the
192 // EachPromise to recursively invoke the provided iterator, which
193 // cause a fatal error: "Cannot resume an already running generator"
194 if ($this->advanceIterator() && !$this->checkIfFinished()) {
195 // Add more pending promises if possible.
196 $this->refillPending();
197 }
198 }
199 private function checkIfFinished()
200 {
201 if (!$this->pending && !$this->iterable->valid()) {
202 // Resolve the promise if there's nothing left to do.
203 $this->aggregate->resolve(null);
204 return \true;
205 }
206 return \false;
207 }
208 }
209