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

197 lines 7.4 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 $this->iterable = \YoastSEO_Vendor\GuzzleHttp\Promise\Create::iterFor($iterable);
52 if (isset($config['concurrency'])) {
53 $this->concurrency = $config['concurrency'];
54 }
55 if (isset($config['fulfilled'])) {
56 $this->onFulfilled = $config['fulfilled'];
57 }
58 if (isset($config['rejected'])) {
59 $this->onRejected = $config['rejected'];
60 }
61 }
62 /** @psalm-suppress InvalidNullableReturnType */
63 public function promise() : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
64 {
65 if ($this->aggregate) {
66 return $this->aggregate;
67 }
68 try {
69 $this->createPromise();
70 /** @psalm-assert Promise $this->aggregate */
71 $this->iterable->rewind();
72 $this->refillPending();
73 } catch (\Throwable $e) {
74 $this->aggregate->reject($e);
75 }
76 /**
77 * @psalm-suppress NullableReturnStatement
78 */
79 return $this->aggregate;
80 }
81 private function createPromise() : void
82 {
83 $this->mutex = \false;
84 $this->aggregate = new \YoastSEO_Vendor\GuzzleHttp\Promise\Promise(function () : void {
85 if ($this->checkIfFinished()) {
86 return;
87 }
88 \reset($this->pending);
89 // Consume a potentially fluctuating list of promises while
90 // ensuring that indexes are maintained (precluding array_shift).
91 while ($promise = \current($this->pending)) {
92 \next($this->pending);
93 $promise->wait();
94 if (\YoastSEO_Vendor\GuzzleHttp\Promise\Is::settled($this->aggregate)) {
95 return;
96 }
97 }
98 });
99 // Clear the references when the promise is resolved.
100 $clearFn = function () : void {
101 $this->iterable = $this->concurrency = $this->pending = null;
102 $this->onFulfilled = $this->onRejected = null;
103 $this->nextPendingIndex = 0;
104 };
105 $this->aggregate->then($clearFn, $clearFn);
106 }
107 private function refillPending() : void
108 {
109 if (!$this->concurrency) {
110 // Add all pending promises.
111 while ($this->addPending() && $this->advanceIterator()) {
112 }
113 return;
114 }
115 // Add only up to N pending promises.
116 $concurrency = \is_callable($this->concurrency) ? ($this->concurrency)(\count($this->pending)) : $this->concurrency;
117 $concurrency = \max($concurrency - \count($this->pending), 0);
118 // Concurrency may be set to 0 to disallow new promises.
119 if (!$concurrency) {
120 return;
121 }
122 // Add the first pending promise.
123 $this->addPending();
124 // Note this is special handling for concurrency=1 so that we do
125 // not advance the iterator after adding the first promise. This
126 // helps work around issues with generators that might not have the
127 // next value to yield until promise callbacks are called.
128 while (--$concurrency && $this->advanceIterator() && $this->addPending()) {
129 }
130 }
131 private function addPending() : bool
132 {
133 if (!$this->iterable || !$this->iterable->valid()) {
134 return \false;
135 }
136 $promise = \YoastSEO_Vendor\GuzzleHttp\Promise\Create::promiseFor($this->iterable->current());
137 $key = $this->iterable->key();
138 // Iterable keys may not be unique, so we use a counter to
139 // guarantee uniqueness
140 $idx = $this->nextPendingIndex++;
141 $this->pending[$idx] = $promise->then(function ($value) use($idx, $key) : void {
142 if ($this->onFulfilled) {
143 ($this->onFulfilled)($value, $key, $this->aggregate);
144 }
145 $this->step($idx);
146 }, function ($reason) use($idx, $key) : void {
147 if ($this->onRejected) {
148 ($this->onRejected)($reason, $key, $this->aggregate);
149 }
150 $this->step($idx);
151 });
152 return \true;
153 }
154 private function advanceIterator() : bool
155 {
156 // Place a lock on the iterator so that we ensure to not recurse,
157 // preventing fatal generator errors.
158 if ($this->mutex) {
159 return \false;
160 }
161 $this->mutex = \true;
162 try {
163 $this->iterable->next();
164 $this->mutex = \false;
165 return \true;
166 } catch (\Throwable $e) {
167 $this->aggregate->reject($e);
168 $this->mutex = \false;
169 return \false;
170 }
171 }
172 private function step(int $idx) : void
173 {
174 // If the promise was already resolved, then ignore this step.
175 if (\YoastSEO_Vendor\GuzzleHttp\Promise\Is::settled($this->aggregate)) {
176 return;
177 }
178 unset($this->pending[$idx]);
179 // Only refill pending promises if we are not locked, preventing the
180 // EachPromise to recursively invoke the provided iterator, which
181 // cause a fatal error: "Cannot resume an already running generator"
182 if ($this->advanceIterator() && !$this->checkIfFinished()) {
183 // Add more pending promises if possible.
184 $this->refillPending();
185 }
186 }
187 private function checkIfFinished() : bool
188 {
189 if (!$this->pending && !$this->iterable->valid()) {
190 // Resolve the promise if there's nothing left to do.
191 $this->aggregate->resolve(null);
192 return \true;
193 }
194 return \false;
195 }
196 }
197