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.
Test Failed
Push — master ( e7e5e4...c04926 )
by
unknown
06:01
created

Promise::wait()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
rs 8.8571
cc 5
eloc 11
nc 4
nop 0
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;
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
104
        if ($value === $this) {
105
            throw new \LogicException(
106
                "Unable to fulfill or reject a promise with itself."
107
            );
108
        }
109
110
        return true;
111
    }
112
113
    public function resolve($value)
114
    {
115
        $this->isStateTransformed = $this->validateState($value, self::STATE_FULFILLED);
116
117
        if ($this->isStateTransformed) {
118
            $this->setState(self::STATE_FULFILLED);
119
            $this->trigger($value);
120
        }
121
    }
122
123
    public function reject($reason)
124
    {
125
        $this->isStateTransformed = $this->validateState($reason, self::STATE_REJECTED);
126
127
        if ($this->isStateTransformed) {
128
            $this->setState(self::STATE_REJECTED);
129
            $this->trigger($reason);
130
        }
131
    }
132
133
    public function wait()
134
    {
135
        $this->waitInPendingState();
136
137
        $q = $this->current instanceof PromiseInterface
138
            ? $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...
139
            : $this->current;
140
141
        if ($this->current instanceof PromiseInterface || $this->currentState() === self::STATE_FULFILLED) {
142
            return $q;
143
        }
144
        else {
145
            throw $q instanceof \Exception
146
                ? $q
147
                : new \Exception($q);
148
        }
149
    }
150
151
    public function cancel($callback = null)
152
    {
153
        if ($this->cancelCallback === null) {
154
            if (!($callback instanceof \Closure)) {
155
                throw new \LogicException(
156
                    sprintf(
157
                        "Default cancellation callback resolver is not set. " .
158
                        "Cancellation mechanism is impossible to invoked because %s is called " .
159
                        "without callback resolver.", __METHOD__
160
                    )
161
                );
162
            }
163
164
            $this->cancelCallback = $callback;
165
        }
166
167
        if ($this->currentState() !== self::STATE_PENDING) {
168
            return;
169
        }
170
171
        if ($this->cancelCallback) {
172
            $cancelCallback = $this->cancelCallback;
173
            $this->cancelCallback = null;
174
175
            try {
176
                $cancelCallback();
177
            } catch (\Exception $e) {
178
                $this->reject($e);
179
            }
180
        }
181
182
        if ($this->currentState() === self::STATE_PENDING) {
183
            $this->reject(new \Exception('Promise has been cancelled.'));
184
        }
185
186
        $e = $this->current instanceof PromiseInterface
187
            ? $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...
188
            : $this->current;
189
190
        if ($this->current instanceof PromiseInterface || $this->currentState() === self::STATE_REJECTED) {
191
            return $e;
192
        }
193
    }
194
195
    public function currentState()
196
    {
197
        return $this->state;
198
    }
199
200
    private function trigger($value)
201
    {
202
        $context = $this->context;
203
        $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...
204
        $this->current = $value;
205
        $this->waitCallback = null;
206
        $this->cancelCallback = null;
207
208
        if (!method_exists($value, 'then')) {
209
            $index = $this->state === self::STATE_FULFILLED ? 1 : 2;
210
211
            Context\ContextStack::create(new TaskQueue)->store(
212
            static function () use ($index, $context, $value) {
213
                foreach ($context as $c) {
214
                    self::invokeContext($c, $index, $value);
215
                }
216
            });
217
218
            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...
219
        } elseif ($value instanceof Promise && $value->currentState() === self::STATE_PENDING) {
220
            $value->context = array_merge($value->context, $context);
221
        } else {
222
            $value->then(
223
            static function ($value) use ($context) {
224
                foreach ($context as $c) {
225
                    self::invokeContext($c, 1, $value);
226
                }
227
            },
228
            static function ($reason) use ($context) {
229
                foreach ($context as $c) {
230
                    self::invokeContext($c, 2, $reason);
231
                }
232
            }
233
            );
234
        }
235
    }
236
237
    private static function invokeContext($context, $index, $value)
238
    {
239
        $promise = $context[0];
240
241
        if ($promise->currentState() !== self::STATE_PENDING) {
242
            return;
243
        }
244
245
        try {
246
            if (isset($context[$index])) {
247
                $promise->resolve($context[$index]($value));
248
            } elseif ($index === 1) {
249
                $promise->resolve($value);
250
            } else {
251
                $promise->reject($value);
252
            }
253
        } catch (\Throwable $e) {
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
254
            $promise->reject($e);
255
        } catch (\Exception $e) {
256
            $promise->reject($e);
257
        }
258
    }
259
260
    private function setState($state)
261
    {
262
        if ($state !== self::STATE_PENDING &&
263
            $state !== self::STATE_FULFILLED &&
264
            $state !== self::STATE_REJECTED) {
265
            throw new \InvalidArgumentException(
266
                sprintf("Parameter 1 of %s must be a valid promise state.", __METHOD__)
267
            );
268
        }
269
270
        $this->state = $state;
271
    }
272
273
    private function waitInPendingState()
274
    {
275
        if ($this->currentState() !== self::STATE_PENDING) {
276
            return;
277
        } else if ($this->waitCallback) {
278
            try {
279
                $waitCallback = $this->waitCallback;
280
                $this->waitCallback = null;
281
                $waitCallback();
282
            } catch (\Exception $e) {
283
                if ($this->currentState() === self::STATE_PENDING) {
284
                    $this->reject($e);
285
                } else {
286
                    throw $e;
287
                }
288
            }
289
        }
290
291
        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...
292
293
        if ($this->currentState() === self::STATE_PENDING) {
294
            $this->reject(
295
                'Invoking the synchronous wait callback resolver didn\'t resolve the current promise'
296
            );
297
        }
298
    }
299
}
300