PromiseTrait::doCancel()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 9

Duplication

Lines 17
Ratio 100 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 0
Metric Value
dl 17
loc 17
ccs 9
cts 9
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 2
nop 1
crap 2
1
<?php
2
3
namespace Dazzle\Promise\Partial;
4
5
use Dazzle\Promise\Helper\CancellationQueue;
6
use Dazzle\Promise\Promise;
7
use Dazzle\Promise\PromiseCancelled;
8
use Dazzle\Promise\PromiseFulfilled;
9
use Dazzle\Promise\PromiseInterface;
10
use Dazzle\Promise\PromiseRejected;
11
use Dazzle\Throwable\Exception\Runtime\UnderflowException;
12
13
trait PromiseTrait
14
{
15
    /**
16
     * Resolve Promise or value.
17
     *
18
     * @param PromiseInterface|mixed $promiseOrValue
19
     * @return PromiseInterface
20
     * @resolves mixed
21
     * @rejects Error|Exception|string|null
22
     * @cancels Error|Exception|string|null
23
     */
24 302
    public static function doResolve($promiseOrValue = null)
25
    {
26 302
        if (!$promiseOrValue instanceof PromiseInterface)
27
        {
28 302
            return new PromiseFulfilled($promiseOrValue);
29
        }
30
31 198
        return $promiseOrValue;
32
    }
33
34
    /**
35
     * Reject Promise or value.
36
     *
37
     * @param PromiseInterface|mixed $promiseOrValue
38
     * @return PromiseInterface
39
     * @rejects Error|Exception|string|null
40
     */
41 143 View Code Duplication
    public static function doReject($promiseOrValue = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
42
    {
43 143
        if (!$promiseOrValue instanceof PromiseInterface)
44
        {
45 139
            return new PromiseRejected($promiseOrValue);
46
        }
47
48
        return self::doResolve($promiseOrValue)->then(function($value) {
49 3
            return new PromiseRejected($value);
50 6
        });
51
    }
52
53
    /**
54
     * Cancel Promise or value.
55
     *
56
     * @param PromiseInterface|mixed $promiseOrValue
57
     * @return PromiseInterface
58
     * @cancels Error|Exception|string|null
59
     */
60 127 View Code Duplication
    public static function doCancel($promiseOrValue = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
61
    {
62 127
        if (!$promiseOrValue instanceof PromiseInterface)
63
        {
64 127
            return new PromiseCancelled($promiseOrValue);
65
        }
66
67 4
        return self::doResolve($promiseOrValue)
68 4
            ->then(
69
                function($value) {
70 2
                    return new PromiseCancelled($value);
71 4
                },
72
                function($value) {
73 2
                    return new PromiseCancelled($value);
74 4
                }
75
            );
76
    }
77
78
    /**
79
     * Return Promise that will resolve only once all the items in $promisesOrValues have resolved.
80
     *
81
     * Return Promise that will resolve only once all the items in $promisesOrValues have resolved. The resolution
82
     * value of the returned promise will be an array containing the resolution values of each of the items in
83
     * $promisesOrValues.
84
     *
85
     * @param PromiseInterface[]|mixed[] $promisesOrValues
86
     * @return PromiseInterface
87
     * @resolves mixed
88
     * @rejects Error|Exception|string|null
89
     * @cancels Error|Exception|string|null
90
     */
91 4
    public static function all($promisesOrValues)
92
    {
93
        return self::map($promisesOrValues, function($val) {
94 3
            return $val;
95 4
        });
96
    }
97
98
    /**
99
     * Initiate a competitive race that allows one winner.
100
     *
101
     * Initiate a competitive race that allows one winner. Returns a promise which is resolved in the same way
102
     * the first settled promise resolves.
103
     *
104
     * @param PromiseInterface[]|mixed[] $promisesOrValues
105
     * @return PromiseInterface
106
     * @resolves mixed
107
     * @rejects Error|Exception|string|null
108
     * @cancels Error|Exception|string|null
109
     */
110 7
    public static function race($promisesOrValues)
111
    {
112 7
        $cancellationQueue = new CancellationQueue();
113
114
        return new Promise(function($resolve, $reject, $cancel) use($promisesOrValues, $cancellationQueue) {
115 7
            self::doResolve($promisesOrValues)
116
                ->done(function($array) use($resolve, $reject, $cancel, $cancellationQueue) {
117 7
                    if (!is_array($array) || !$array)
0 ignored issues
show
Bug Best Practice introduced by
The expression $array of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
118
                    {
119 1
                        $resolve();
120 1
                        return;
121
                    }
122
123
                    $fulfiller = function($value) use($resolve, $cancellationQueue) {
124 3
                        $resolve($value);
125 3
                        $cancellationQueue();
126 6
                    };
127
128
                    $rejecter = function($reason) use($reject, $cancellationQueue) {
129 2
                        $reject($reason);
130 2
                        $cancellationQueue();
131 6
                    };
132
133 6
                    foreach ($array as $promiseOrValue)
134
                    {
135 6
                        $cancellationQueue->enqueue($promiseOrValue);
136
137 6
                        self::doResolve($promiseOrValue)
138 6
                            ->done($fulfiller, $rejecter, $cancel);
139
                    }
140 7
                }, $reject, $cancel);
141 7
        }, $cancellationQueue);
142
    }
143
144
    /**
145
     * Return a promise that will resolve when any one of the items in $promisesOrValues resolves.
146
     *
147
     * Return a promise that will resolve when any one of the items in $promisesOrValues resolves. The resolution value
148
     * of the returned promise will be the resolution value of the triggering item.
149
     *
150
     * @param PromiseInterface[]|mixed[] $promisesOrValues
151
     * @return PromiseInterface
152
     * @resolves mixed
153
     * @rejects Error|Exception|string|null
154
     * @cancels Error|Exception|string|null
155
     */
156 7
    public static function any($promisesOrValues)
157
    {
158 7
        return self::some($promisesOrValues, 1)
159
            ->then(function($val) {
160 4
                return array_shift($val);
161 7
            });
162
    }
163
164
    /**
165
     * Return Promise that will resolve when $howMany of the supplied items in $promisesOrValues resolve.
166
     *
167
     * Return Promise that will resolve when $howMany of the supplied items in $promisesOrValues resolve. The resolution
168
     * value of the returned promise will be an array of length $howMany containing the resolution values of
169
     * the triggering items.
170
     *
171
     * @param PromiseInterface[]|mixed[] $promisesOrValues
172
     * @param int $howMany
173
     * @return PromiseInterface
174
     * @resolves mixed
175
     * @rejects Error|Exception|string|null
176
     * @cancels Error|Exception|string|null
177
     */
178 18
    public static function some($promisesOrValues, $howMany)
179
    {
180 18
        $cancellationQueue = new CancellationQueue();
181
182
        return new Promise(function($resolve, $reject, $cancel) use($promisesOrValues, $howMany, $cancellationQueue) {
183 18
            self::doResolve($promisesOrValues)
184
                ->done(function($array) use($resolve, $reject, $cancel, $howMany, $cancellationQueue) {
185 18
                    if (!is_array($array) || $howMany < 1)
186
                    {
187 1
                        $resolve([]);
188 1
                        return;
189
                    }
190
191 17
                    $len = count($array);
192
193 17
                    if ($len < $howMany)
194
                    {
195 3
                        throw new UnderflowException(
196 3
                            sprintf('Input array must contain at least %d items but contains only %s items.', $howMany, $len)
197
                        );
198
                    }
199
200 14
                    $toResolve = $howMany;
201 14
                    $toReject  = ($len - $toResolve) + 1;
202 14
                    $values    = [];
203 14
                    $reasons   = [];
204
205 14
                    foreach ($array as $i=>$promiseOrValue)
206
                    {
207 View Code Duplication
                        $fulfiller = function($val) use($i, &$values, &$toResolve, $toReject, $resolve, $cancellationQueue) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
208 10
                            if ($toResolve < 1 || $toReject < 1)
209
                            {
210 5
                                return;
211
                            }
212
213 10
                            $values[$i] = $val;
214
215 10
                            if (0 === --$toResolve)
216
                            {
217 9
                                $resolve($values);
218 9
                                $cancellationQueue();
219
                            }
220 14
                        };
221
222 View Code Duplication
                        $rejecter = function($reason) use($i, &$reasons, &$toReject, $toResolve, $reject, $cancellationQueue) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
223 4
                            if ($toResolve < 1 || $toReject < 1)
224
                            {
225 1
                                return;
226
                            }
227
228 4
                            $reasons[$i] = $reason;
229
230 4
                            if (0 === --$toReject)
231
                            {
232 3
                                $reject($reasons);
233 3
                                $cancellationQueue();
234
                            }
235 14
                        };
236
237 14
                        $canceller = $cancel;
238
239 14
                        $cancellationQueue->enqueue($promiseOrValue);
240
241 14
                        self::doResolve($promiseOrValue)
242 14
                            ->done($fulfiller, $rejecter, $canceller);
243
                    }
244 18
                }, $reject, $cancel);
245 18
        }, $cancellationQueue);
246
    }
247
248
    /**
249
     * Map promises and/or values using specified $mapFunc.
250
     *
251
     * @see array_map
252
     *
253
     * @param PromiseInterface[]|mixed[] $promisesOrValues
254
     * @param callable $mapFunc
255
     * @return PromiseInterface
256
     * @resolves mixed
257
     * @rejects Error|Exception|string|null
258
     * @cancels Error|Exception|string|null
259
     */
260 10
    public static function map($promisesOrValues, callable $mapFunc)
261
    {
262 10
        $cancellationQueue = new CancellationQueue();
263
264
        return new Promise(function($resolve, $reject, $cancel) use($promisesOrValues, $mapFunc, $cancellationQueue) {
265 10
            self::doResolve($promisesOrValues)
266
                ->done(function($array) use($resolve, $reject, $cancel, $mapFunc, $cancellationQueue) {
267 10
                    if (!is_array($array) || !$array)
0 ignored issues
show
Bug Best Practice introduced by
The expression $array of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
268
                    {
269 1
                        $resolve([]);
270 1
                        return;
271
                    }
272
273 9
                    $toResolve = count($array);
274 9
                    $values    = [];
275
276 9
                    foreach ($array as $i=>$promiseOrValue)
277
                    {
278 9
                        $cancellationQueue->enqueue($promiseOrValue);
279
280 9
                        self::doResolve($promiseOrValue)
281 9
                            ->then($mapFunc)
282 9
                            ->done(
283
                                function($mapped) use($i, &$values, &$toResolve, $resolve) {
284 8
                                    $values[$i] = $mapped;
285 8
                                    if (0 === --$toResolve)
286
                                    {
287 6
                                        $resolve($values);
288
                                    }
289 9
                                },
290 9
                                $reject,
291 9
                                $cancel
292
                            );
293
                    }
294 10
                }, $reject, $cancel);
295 10
        }, $cancellationQueue);
296
    }
297
298
    /**
299
     * Reduce Promises and/or values using $reduceFunc with $initialValue being Promise or primitive value.
300
     *
301
     * @see array_reduce
302
     *
303
     * @param PromiseInterface[]|mixed[] $promisesOrValues
304
     * @param callable $reduceFunc
305
     * @param PromiseInterface|mixed|null $initialValue
306
     * @return PromiseInterface
307
     * @resolves mixed
308
     * @rejects Error|Exception|string|null
309
     * @cancels Error|Exception|string|null
310
     */
311 15
    public static function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null)
312
    {
313 15
        $cancellationQueue = new CancellationQueue();
314
315
        return new Promise(function($resolve, $reject, $cancel) use($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) {
316 15
            self::doResolve($promisesOrValues)
317
                ->done(function($array) use($reduceFunc, $initialValue, $resolve, $reject, $cancel, $cancellationQueue) {
318 15
                    if (!is_array($array))
319
                    {
320
                        $array = [];
321
                    }
322
323 15
                    $total = count($array);
324 15
                    $i = 0;
325
326
                    // Wrap the supplied $reduceFunc with one that handles promises and then delegates to the supplied.
327
                    //
328
                    $wrappedReduceFunc = function(PromiseInterface $current, $val) use($reduceFunc, $cancellationQueue, $total, &$i) {
329 12
                        $cancellationQueue->enqueue($val);
330
                        return $current
331
                            ->then(function($c) use($reduceFunc, $total, &$i, $val) {
332 12
                                return self::doResolve($val)
333 12
                                    ->then(function($value) use($reduceFunc, $total, &$i, $c) {
334 11
                                        return $reduceFunc($c, $value, $i++, $total);
335 12
                                    });
336 12
                            });
337 15
                    };
338
339 15
                    $initialValue = self::doResolve($initialValue);
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $initialValue, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
340
341 15
                    $cancellationQueue->enqueue($initialValue);
342
343 15
                    array_reduce($array, $wrappedReduceFunc, $initialValue)
344 15
                        ->done($resolve, $reject, $cancel);
345
346 15
                }, $reject, $cancel);
347 15
        }, $cancellationQueue);
348
    }
349
}
350