PluginProbe
Media Cloud Sync / 1.0.2
Media Cloud Sync v1.0.2
1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 All 34 releases
media-cloud-sync / includes / sdk / s3 / GuzzleHttp / Promise / EachPromise.php

EachPromise.php in Media Cloud Sync 1.0.2, at includes/sdk/s3/GuzzleHttp/Promise/EachPromise.php

201 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Dudlewebs\WPMCS\s3\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 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 = 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 $this->aggregate->reject($e);
72 } catch (\Exception $e) {
73 $this->aggregate->reject($e);
74 }
75 /**
76 * @psalm-suppress NullableReturnStatement
77 * @phpstan-ignore-next-line
78 */
79 return $this->aggregate;
80 }
81 private function createPromise()
82 {
83 $this->mutex = \false;
84 $this->aggregate = new Promise(function () {
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 (Is::settled($this->aggregate)) {
95 return;
96 }
97 }
98 });
99 // Clear the references when the promise is resolved.
100 $clearFn = function () {
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()
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) ? \call_user_func($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()
132 {
133 if (!$this->iterable || !$this->iterable->valid()) {
134 return \false;
135 }
136 $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) {
142 if ($this->onFulfilled) {
143 \call_user_func($this->onFulfilled, $value, $key, $this->aggregate);
144 }
145 $this->step($idx);
146 }, function ($reason) use($idx, $key) {
147 if ($this->onRejected) {
148 \call_user_func($this->onRejected, $reason, $key, $this->aggregate);
149 }
150 $this->step($idx);
151 });
152 return \true;
153 }
154 private function advanceIterator()
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 } catch (\Exception $e) {
171 $this->aggregate->reject($e);
172 $this->mutex = \false;
173 return \false;
174 }
175 }
176 private function step($idx)
177 {
178 // If the promise was already resolved, then ignore this step.
179 if (Is::settled($this->aggregate)) {
180 return;
181 }
182 unset($this->pending[$idx]);
183 // Only refill pending promises if we are not locked, preventing the
184 // EachPromise to recursively invoke the provided iterator, which
185 // cause a fatal error: "Cannot resume an already running generator"
186 if ($this->advanceIterator() && !$this->checkIfFinished()) {
187 // Add more pending promises if possible.
188 $this->refillPending();
189 }
190 }
191 private function checkIfFinished()
192 {
193 if (!$this->pending && !$this->iterable->valid()) {
194 // Resolve the promise if there's nothing left to do.
195 $this->aggregate->resolve(null);
196 return \true;
197 }
198 return \false;
199 }
200 }
201