Promise::failure()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Dazzle\Promise;
4
5
use Dazzle\Promise\Partial\PromiseTrait;
6
use Error;
7
use Exception;
8
9
class Promise implements PromiseInterface
10
{
11
    use PromiseTrait;
12
13
    /**
14
     * @var PromiseInterface|null
15
     */
16
    protected $result;
17
18
    /**
19
     * @var callable[]
20
     */
21
    protected $handlers;
22
23
    /**
24
     * @var callable
25
     */
26
    protected $canceller;
27
28
    /**
29
     * @var int
30
     */
31
    protected $currentCancellations;
32
33
    /**
34
     * @var int
35
     */
36
    protected $requiredCancellations;
37
38
    /**
39
     * @param callable|null $resolver
40
     * @param callable|null $canceller
41
     */
42 374
    public function __construct(callable $resolver = null, callable $canceller = null)
43
    {
44 374
        $this->result = null;
45 374
        $this->handlers = [];
46
        $this->canceller = function($reason = null) use($canceller) {
47
            try
48
            {
49 108
                return $canceller !== null && ($result = $canceller($reason)) instanceof self ? $result : $this;
0 ignored issues
show
Bug introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
50
            }
51
            catch (Error $ex)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
52
            {}
53
            catch (Exception $ex)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
54
            {}
55
            return $this;
56
        };
57 374
        $this->currentCancellations = 0;
58 374
        $this->requiredCancellations = 0;
59
60 374
        $this->mutate($resolver);
61 374
    }
62
63
    /**
64
     *
65
     */
66 103
    public function __destruct()
67
    {
68 103
        unset($this->result);
69 103
        unset($this->handlers);
70 103
        unset($this->canceller);
71 103
        unset($this->currentCancellations);
72 103
        unset($this->requiredCancellations);
73 103
    }
74
75
    /**
76
     * @override
77
     * @inheritDoc
78
     */
79 222
    public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onCancel = null)
80
    {
81 222
        if (null !== $this->result)
82
        {
83 130
            return $this->getResult()->then($onFulfilled, $onRejected, $onCancel);
84
        }
85
86 96
        if (null !== $this->canceller)
87
        {
88 96
            $this->requiredCancellations++;
89
90
            $canceller = function($reason = null) {
91 39
                if (++$this->currentCancellations >= $this->requiredCancellations)
92
                {
93 35
                    return $this->cancel($reason);
94
                }
95 12
                return null;
96 96
            };
97
        }
98
        else
99
        {
100
            $canceller = null;
101
        }
102
103
        return new static(function($resolve, $reject, $cancel) use($onFulfilled, $onRejected, $onCancel) {
104
105
            $this->handlers[] = function(PromiseInterface $promise) use($resolve, $reject, $cancel, $onFulfilled, $onRejected, $onCancel) {
106
                $promise
107 86
                    ->then($onFulfilled, $onRejected, $onCancel)
108 86
                    ->done($resolve, $reject, $cancel)
109
                ;
110 84
            };
111 96
        }, $canceller);
112
    }
113
114
    /**
115
     * @override
116
     * @inheritDoc
117
     */
118 80
    public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onCancel = null)
119
    {
120 80
        if (null !== $this->result)
121
        {
122 39
            $this->getResult()->done($onFulfilled, $onRejected, $onCancel);
123
        }
124
125
        $this->handlers[] = function(PromiseInterface $promise) use($onFulfilled, $onRejected, $onCancel) {
126
            $promise
127 37
                ->done($onFulfilled, $onRejected, $onCancel);
128 17
        };
129 54
    }
130
131
    /**
132
     * @override
133
     * @inheritDoc
134
     */
135 18
    public function spread(callable $onFulfilled = null, callable $onRejected = null, callable $onCancel = null)
136
    {
137 18
        return $this->then(
138
            function($values) use($onFulfilled) {
139 6
                return $onFulfilled(...((array) $values));
140 18
            },
141
            function($rejections) use($onRejected) {
142 6
                return $onRejected(...((array) $rejections));
143 18
            },
144
            function($reasons) use($onCancel) {
145 6
                return $onCancel(...((array) $reasons));
146 18
            }
147
        );
148
    }
149
150
    /**
151
     * @override
152
     * @inheritDoc
153
     */
154 8
    public function success(callable $onSuccess)
155
    {
156 8
        return $this->then($onSuccess);
157
    }
158
159
    /**
160
     * @override
161
     * @inheritDoc
162
     */
163 10
    public function failure(callable $onFailure)
164
    {
165 10
        return $this->then(null, $onFailure);
166
    }
167
168
    /**
169
     * @override
170
     * @inheritDoc
171
     */
172 8
    public function abort(callable $onCancel)
173
    {
174 8
        return $this->then(null, null, $onCancel);
175
    }
176
177
    /**
178
     * @override
179
     * @inheritDoc
180
     */
181 58
    public function always(callable $onFulfilledOrRejected)
182
    {
183 58
        return $this->then(
184
            function($value) use($onFulfilledOrRejected) {
185
                return self::doResolve($onFulfilledOrRejected())->then(function() use($value) {
186 12
                    return $value;
187 16
                });
188 58
            },
189
            function($reason) use($onFulfilledOrRejected) {
190
                return self::doResolve($onFulfilledOrRejected())->then(function() use($reason) {
191 12
                    return new PromiseRejected($reason);
192 16
                });
193 58
            },
194
            function($reason) use($onFulfilledOrRejected) {
195
                return self::doResolve($onFulfilledOrRejected())->then(function() use($reason) {
196 12
                    return new PromiseCancelled($reason);
197 14
                });
198 58
            }
199
        );
200
    }
201
202
    /**
203
     * @override
204
     * @inheritDoc
205
     */
206 34
    public function isPending()
207
    {
208 34
        return $this->result === null;
209
    }
210
211
    /**
212
     * @override
213
     * @inheritDoc
214
     */
215 8
    public function isFulfilled()
216
    {
217 8
        return !$this->isPending() && $this->result->isFulfilled();
218
    }
219
220
    /**
221
     * @override
222
     * @inheritDoc
223
     */
224 8
    public function isRejected()
225
    {
226 8
        return !$this->isPending() && $this->result->isRejected();
227
    }
228
229
    /**
230
     * @override
231
     * @inheritDoc
232
     */
233 10
    public function isCancelled()
234
    {
235 10
        return !$this->isPending() && $this->result->isCancelled();
236
    }
237
238
    /**
239
     * @override
240
     * @inheritDoc
241
     */
242
    public function getPromise()
243
    {
244
        return $this;
245
    }
246
247
    /**
248
     * @override
249
     * @inheritDoc
250
     */
251 144
    public function resolve($value = null)
252
    {
253 144
        if (null !== $this->result || $value === $this)
254
        {
255 10
            return $this->result;
256
        }
257
258 142
        return $this->settle(self::doResolve($value));
259
    }
260
261
    /**
262
     * @override
263
     * @inheritDoc
264
     */
265 114
    public function reject($reason = null)
266
    {
267 114
        if (null !== $this->result || $reason === $this)
268
        {
269 6
            return $this->result;
270
        }
271
272 112
        return $this->settle(self::doReject($reason));
273
    }
274
275
    /**
276
     * @override
277
     * @inheritDoc
278
     */
279 121
    public function cancel($reason = null)
280
    {
281 121
        if (null !== $this->result || $reason === $this)
282
        {
283 50
            return $this->result;
284
        }
285
286 108
        $target = $this;
287
288 108
        if (null !== $this->canceller)
289
        {
290 108
            $canceller = $this->canceller;
291 108
            $this->canceller = null;
292 108
            $target = $canceller($reason);
293
        }
294
295 108
        if ($target === $this)
296
        {
297 108
            return $target->settle(self::doCancel($reason));
298
        }
299
300
        return $target;
301
    }
302
303
    /**
304
     * Return primitive value associated with Promise.
305
     *
306
     * @return mixed|null
307
     */
308
    protected function getValue()
309
    {
310
        return $this->isFulfilled() ? $this->result->getValue() : null;
311
    }
312
313
    /**
314
     * Return rejection or cancellation reason for Promise.
315
     *
316
     * @return Error|Exception|string|null
317
     */
318
    protected function getReason()
319
    {
320
        return ($this->isRejected() || $this->isCancelled()) ? $this->result->getReason() : null;
321
    }
322
323
    /**
324
     * Settle Promise with another Promise.
325
     *
326
     * @see PromiseInterface::resolve
327
     * @see PromiseInterface::reject
328
     * @see PromiseInterface::cancel
329
     *
330
     * @param PromiseInterface $promise
331
     * @return PromiseInterface
332
     * @resolves mixed|null
333
     * @rejects Error|Exception|string|null
334
     * @cancels Error|Exception|string|null
335
     */
336 330
    protected function settle(PromiseInterface $promise)
337
    {
338 330
        $handlers = $this->handlers;
339
340 330
        $this->result = $promise;
341 330
        $this->handlers = [];
342
343 330
        foreach ($handlers as $handler)
344
        {
345 114
            $handler($promise);
346
        }
347
348 312
        return $promise;
349
    }
350
351
    /**
352
     * Get Promise result. Returns fulfilled, rejected or cancelled Promise for settled Promises or null for pending.
353
     *
354
     * @return PromiseInterface|null
355
     */
356 168
    protected function getResult()
357
    {
358 168
        while ($this->result instanceof Promise && null !== $this->result->result)
359
        {
360 1
            $this->result = $this->result->result;
361
        }
362
363 168
        return $this->result;
364
    }
365
366
    /**
367
     * Mutate resolver.
368
     *
369
     * @param callable|null $resolver
370
     */
371 374
    protected function mutate(callable $resolver = null)
372
    {
373 374
        if ($resolver === null)
374
        {
375 24
            return;
376
        }
377
378
        try
379
        {
380 350
            $resolver(
381
                function ($value = null) {
382 140
                    $this->resolve($value);
383 350
                },
384
                function ($reason = null) {
385 106
                    $this->reject($reason);
386 350
                },
387 350
                function ($reason = null) {
388 98
                    $this->cancel($reason);
389 350
                }
390
            );
391
        }
392 4
        catch (Error $ex)
393
        {
394
            $this->reject($ex);
395
        }
396 4
        catch (Exception $ex)
397
        {
398 4
            $this->reject($ex);
399
        }
400 350
    }
401
}
402