PluginProbe
Media Cloud Sync / 1.3.11
Media Cloud Sync v1.3.11
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.3.11, at includes/sdk/s3/GuzzleHttp/Promise/EachPromise.php

197 lines 7.1 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 Dudlewebs\WPMCS\s3\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 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 = 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() : 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 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 (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 = 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 (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