PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.1
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.1
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 28.1, at vendor_prefixed/guzzlehttp/promises/src/EachPromise.php

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