PluginProbe
StockPack – Stock photos and AI images from Unsplash, Adobe Stock, Freepik and more / 2.4.3
StockPack – Stock photos and AI images from Unsplash, Adobe Stock, Freepik and more v2.4.3
3.7.2 3.7.1 3.7.0 3.6.0 3.6.1 3.5.2 trunk 2.0 2.1 2.2 2.3 2.4 2.4.1 2.4.2 2.4.3 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.6.0 2.6.1 All 73 releases
stockpack / vendor / guzzlehttp / promises / src / EachPromise.php

EachPromise.php in StockPack – Stock photos and AI images from Unsplash, Adobe Stock, Freepik and more 2.4.3, at vendor/guzzlehttp/promises/src/EachPromise.php

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