Passed
Push — master ( 89369f...5a90e9 )
by Vladimir
03:51
created

SyncPromise::resolve()   C

Complexity

Conditions 8
Paths 7

Size

Total Lines 32
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 8.0052

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 22
cts 23
cp 0.9565
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 22
nc 7
nop 1
crap 8.0052
1
<?php
2
namespace GraphQL\Executor\Promise\Adapter;
3
4
use GraphQL\Utils\Utils;
5
6
/**
7
 * Class SyncPromise
8
 *
9
 * Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode
10
 * (using queue to defer promises execution)
11
 *
12
 * @package GraphQL\Executor\Promise\Adapter
13
 */
14
class SyncPromise
15
{
16
    const PENDING = 'pending';
17
    const FULFILLED = 'fulfilled';
18
    const REJECTED = 'rejected';
19
20
    /**
21
     * @var \SplQueue
22
     */
23
    public static $queue;
24
25 231
    public static function getQueue()
26
    {
27 231
        return self::$queue ?: self::$queue = new \SplQueue();
28
    }
29
30 209
    public static function runQueue()
31
    {
32 209
        $q = self::$queue;
33 209
        while ($q && !$q->isEmpty()) {
34 209
            $task = $q->dequeue();
35 209
            $task();
36
        }
37 209
    }
38
39
    public $state = self::PENDING;
40
41
    public $result;
42
43
    /**
44
     * Promises created in `then` method of this promise and awaiting for resolution of this promise
45
     * @var array
46
     */
47
    private $waiting = [];
48
49 49
    public function reject($reason)
50
    {
51 49
        if (!$reason instanceof \Exception && !$reason instanceof \Throwable) {
52 1
            throw new \Exception('SyncPromise::reject() has to be called with an instance of \Throwable');
53
        }
54
55 49
        switch ($this->state) {
56 49
            case self::PENDING:
57 44
                $this->state = self::REJECTED;
58 44
                $this->result = $reason;
59 44
                $this->enqueueWaitingPromises();
60 44
                break;
61 15
            case self::REJECTED:
62 10
                if ($reason !== $this->result) {
63 10
                    throw new \Exception("Cannot change rejection reason");
64
                }
65
                break;
66 5
            case self::FULFILLED:
67 5
                throw new \Exception("Cannot reject fulfilled promise");
68
        }
69 44
        return $this;
70
    }
71
72 246
    public function resolve($value)
73
    {
74 246
        switch ($this->state) {
75 246
            case self::PENDING:
76 241
                if ($value === $this) {
77 1
                    throw new \Exception("Cannot resolve promise with self");
78
                }
79 241
                if (is_object($value) && method_exists($value, 'then')) {
80 43
                    $value->then(
81
                        function($resolvedValue) {
82 43
                            $this->resolve($resolvedValue);
83 43
                        },
84
                        function($reason) {
85 26
                            $this->reject($reason);
86 43
                        }
87
                    );
88 43
                    return $this;
89
                }
90
91 241
                $this->state = self::FULFILLED;
92 241
                $this->result = $value;
93 241
                $this->enqueueWaitingPromises();
94 241
                break;
95 15
            case self::FULFILLED:
96 5
                if ($this->result !== $value) {
97 5
                    throw new \Exception("Cannot change value of fulfilled promise");
98
                }
99
                break;
100 10
            case self::REJECTED:
101 10
                throw new \Exception("Cannot resolve rejected promise");
102
        }
103 241
        return $this;
104
    }
105
106 210
    public function then(callable $onFulfilled = null, callable $onRejected = null)
107
    {
108 210
        if ($this->state === self::REJECTED && !$onRejected) {
109 5
            return $this;
110
        }
111 210
        if ($this->state === self::FULFILLED && !$onFulfilled) {
112 152
            return $this;
113
        }
114 210
        $tmp = new self();
115 210
        $this->waiting[] = [$tmp, $onFulfilled, $onRejected];
116
117 210
        if ($this->state !== self::PENDING) {
118 198
            $this->enqueueWaitingPromises();
119
        }
120
121 210
        return $tmp;
122
    }
123
124 251
    private function enqueueWaitingPromises()
125
    {
126 251
        Utils::invariant($this->state !== self::PENDING, 'Cannot enqueue derived promises when parent is still pending');
127
128 251
        foreach ($this->waiting as $descriptor) {
129 209
            self::getQueue()->enqueue(function () use ($descriptor) {
130
                /** @var $promise self */
131 209
                list($promise, $onFulfilled, $onRejected) = $descriptor;
132
133 209
                if ($this->state === self::FULFILLED) {
134
                    try {
135 205
                        $promise->resolve($onFulfilled ? $onFulfilled($this->result) : $this->result);
136 14
                    } catch (\Exception $e) {
137 14
                        $promise->reject($e);
138
                    } catch (\Throwable $e) {
139 205
                        $promise->reject($e);
140
                    }
141 34
                } else if ($this->state === self::REJECTED) {
142
                    try {
143 34
                        if ($onRejected) {
144 34
                            $promise->resolve($onRejected($this->result));
145
                        } else {
146 34
                            $promise->reject($this->result);
147
                        }
148 2
                    } catch (\Exception $e) {
149 2
                        $promise->reject($e);
150
                    } catch (\Throwable $e) {
151
                        $promise->reject($e);
152
                    }
153
                }
154 209
            });
155
        }
156 251
        $this->waiting = [];
157 251
    }
158
}
159