GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 8953ec...b67269 )
by
unknown
07:42
created

Promise::trigger()   C

Complexity

Conditions 8
Paths 5

Size

Total Lines 40
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 40
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 24
nc 5
nop 1
1
<?php
2
3
namespace Gandung\Promise;
4
5
use TaskQueue\TaskQueue;
6
7
class Promise implements PromiseInterface
8
{
9
    /**
10
     * @var integer
11
     */
12
    private $state = self::STATE_PENDING;
13
14
    /**
15
     * @var mixed
16
     */
17
    private $current;
18
19
    /**
20
     * @var array
21
     */
22
    private $context = [];
23
24
    /**
25
     * @var \Closure
26
     */
27
    private $waitCallback;
28
29
    /**
30
     * @var \Closure
31
     */
32
    private $cancelCallback;
33
34
    /**
35
     * @var boolean
36
     */
37
    private $isStateTransformed;
38
39
    public function then($onFulfilled = null, $onRejected = null)
40
    {
41
        if ($this->state === self::STATE_PENDING) {
42
            $q = new Promise();
43
            $this->context[] = [$q, $onFulfilled, $onRejected];
44
45
            return $q;
46
        }
47
48
        if ($this->state === self::STATE_FULFILLED) {
49
            return $onFulfilled
50
                ? (new FulfilledPromise($this->current))->then($onFulfilled, null)
51
                : new FulfilledPromise($this->current);
52
        }
53
54
        return $onRejected
55
            ? (new RejectedPromise($this->current))->then(null, $onRejected)
56
            : new RejectedPromise($this->current);
57
    }
58
59
    public function setWaitCallback(\Closure $callback = null)
60
    {
61
        if (!($callback instanceof \Closure)) {
62
            throw new \InvalidArgumentException(
63
                sprintf("Parameter 1 of %s must be a valid callback.", __METHOD__)
64
            );
65
        }
66
67
        $this->waitCallback = $callback;
68
    }
69
70
    public function setCancelCallback(\Closure $callback = null)
71
    {
72
        if (!($callback instanceof \Closure)) {
73
            throw new \InvalidArgumentException(
74
                sprintf("Parameter 1 of %s must be a valid callback.", __METHOD__)
75
            );
76
        }
77
78
        $this->cancelCallback = $callback;
79
    }
80
81
    private function validateState($value, $state)
82
    {
83
        if ($this->state !== self::STATE_PENDING) {
84
            if ($value === $this->current && $state === $this->state) {
85
                return false;
86
            }
87
88
            $prevStatus = $this->state;
89
            $currentStatus = $state;
90
91
            throw $this->state === $state
92
                ? new \LogicException(
93
                    sprintf("State of the promise is already %s", $prevStatus == 4 ? 'fulfilled' : 'rejected')
94
                )
95
                : new \LogicException(
96
                    sprintf(
97
                        "Unable to change %s promise to %s",
98
                        $prevStatus == 4 ? 'fulfilled' : 'rejected',
99
                        $currentStatus == 4 ? 'fulfilled' : 'rejected'
100
                    )
101
                );
102
103
            return false;
0 ignored issues
show
Unused Code introduced by
return false; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
104
        }
105
106
        if ($value === $this) {
107
            throw new \LogicException(
108
                "Unable to fulfill or reject a promise with itself."
109
            );
110
111
            return false;
0 ignored issues
show
Unused Code introduced by
return false; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
112
        }
113
114
        return true;
115
    }
116
117
    public function resolve($value)
118
    {
119
        $this->isStateTransformed = $this->validateState($value, self::STATE_FULFILLED);
120
121
        if ($this->isStateTransformed) {
122
            $this->setState(self::STATE_FULFILLED);
123
            $this->trigger($value);
124
        }
125
    }
126
127
    public function reject($reason)
128
    {
129
        $this->isStateTransformed = $this->validateState($reason, self::STATE_REJECTED);
130
131
        if ($this->isStateTransformed) {
132
            $this->setState(self::STATE_REJECTED);
133
            $this->trigger($reason);
134
        }
135
    }
136
137
    public function wait($callback = null)
138
    {
139
        if ($this->waitCallback === null) {
140
            if (!($callback instanceof \Closure)) {
141
                throw new \LogicException(
142
                    sprintf(
143
                        "Default waiting callback resolver is not set. " .
144
                        "Synchronous wait mechanism is impossible to invoked because %s is called " .
145
                        "without callback resolver.", __METHOD__
146
                    )
147
                );
148
            }
149
150
            $this->waitCallback = $callback;
151
        }
152
153
        if ($this->currentState() !== self::STATE_PENDING) {
154
            return;
155
        }
156
157
        try {
158
            $waitCallback = $this->waitCallback;
159
            $this->waitCallback = null;
160
            $waitCallback();
161
        } catch (\Exception $e) {
162
            if ($this->currentState() === self::STATE_PENDING) {
163
                $this->reject($e);
164
            } else {
165
                throw $e;
166
            }
167
        }
168
169
        Context\ContextStack::getQueueHandler()->run();
0 ignored issues
show
Bug introduced by
The method run cannot be called on \Gandung\Promise\Context...tack::getQueueHandler() (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
170
171
        if ($this->currentState() === self::STATE_PENDING) {
172
            $this->reject(
173
                'Invoking the synchronous wait callback resolver didn\'t resolve the current promise'
174
            );
175
        }
176
177
        $q = $this->current instanceof PromiseInterface
178
            ? $this->current->wait()
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Gandung\Promise\PromiseInterface as the method wait() does only exist in the following implementations of said interface: Gandung\Promise\Promise.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
179
            : $this->current;
180
181
        if ($this->current instanceof PromiseInterface || $this->currentState() === self::STATE_FULFILLED) {
182
            return $q;
183
        }
184
    }
185
186
    public function cancel($callback = null)
187
    {
188
        if ($this->cancelCallback === null) {
189
            if (!($callback instanceof \Closure)) {
190
                throw new \LogicException(
191
                    sprintf(
192
                        "Default cancellation callback resolver is not set. " .
193
                        "Cancellation mechanism is impossible to invoked because %s is called " .
194
                        "without callback resolver.", __METHOD__
195
                    )
196
                );
197
            }
198
199
            $this->cancelCallback = $callback;
200
        }
201
202
        if ($this->currentState() !== self::STATE_PENDING) {
203
            return;
204
        }
205
206
        if ($this->cancelCallback) {
207
            $cancelCallback = $this->cancelCallback;
208
            $this->cancelCallback = null;
209
210
            try {
211
                $cancelCallback();
212
            } catch (\Exception $e) {
213
                $this->reject($e);
214
            }
215
        }
216
217
        if ($this->currentState() === self::STATE_PENDING) {
218
            $this->reject(new \Exception('Promise has been cancelled.'));
219
        }
220
221
        $e = $this->current instanceof PromiseInterface
222
            ? $this->current->cancel()
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Gandung\Promise\PromiseInterface as the method cancel() does only exist in the following implementations of said interface: Gandung\Promise\Promise.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
223
            : $this->current;
224
225
        if ($this->current instanceof PromiseInterface || $this->currentState() === self::STATE_REJECTED) {
226
            return $e;
227
        }
228
    }
229
230
    public function currentState()
231
    {
232
        return $this->state;
233
    }
234
235
    private function trigger($value)
236
    {
237
        if ($this->state === self::STATE_PENDING) {
238
            throw new \LogicException(
239
                "Cannot resolving or rejecting promise when in pending state."
240
            );
241
        }
242
243
        $context = $this->context;
244
        $this->context = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $context.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
245
        $this->current = $value;
246
        
247
        if (!method_exists($value, 'then')) {
248
            $index = $this->state === self::STATE_FULFILLED ? 1 : 2;
249
250
            Context\ContextStack::create(new TaskQueue)->store(
251
            static function () use ($index, $context, $value) {
252
                foreach ($context as $c) {
253
                    self::invokeContext($c, $index, $value);
254
                }
255
            });
256
257
            Context\ContextStack::getQueueHandler()->run();
0 ignored issues
show
Bug introduced by
The method run cannot be called on \Gandung\Promise\Context...tack::getQueueHandler() (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
258
        } elseif ($value instanceof Promise) {
259
            $value->context = array_merge($value->context, $context);
260
        } else {
261
            $value->then(
262
            static function ($value) use ($context) {
263
                foreach ($context as $c) {
264
                    self::invokeContext($c, 1, $value);
265
                }
266
            },
267
            static function ($reason) use ($context) {
268
                foreach ($context as $c) {
269
                    self::invokeContext($c, 2, $reason);
270
                }
271
            }
272
            );
273
        }
274
    }
275
276
    private static function invokeContext($context, $index, $value)
277
    {
278
        $promise = $context[0];
279
280
        if ($promise->currentState() !== self::STATE_PENDING) {
281
            return;
282
        }
283
284
        try {
285
            if (isset($context[$index])) {
286
                $promise->resolve($context[$index]($value));
287
            } elseif ($index === 1) {
288
                $promise->resolve($value);
289
            } else {
290
                $promise->reject($value);
291
            }
292
        } catch (\Exception $e) {
293
            $promise->reject($e);
294
        }
295
    }
296
297
    private function setState($state)
298
    {
299
        if ($state !== self::STATE_PENDING &&
300
            $state !== self::STATE_FULFILLED &&
301
            $state !== self::STATE_REJECTED) {
302
            throw new \InvalidArgumentException(
303
                sprintf("Parameter 1 of %s must be a valid promise state.", __METHOD__)
304
            );
305
        }
306
307
        $this->state = $state;
308
    }
309
}
310