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 ( c04926...3cf926 )
by
unknown
07:44
created

Promise::cancel()   C

Complexity

Conditions 8
Paths 25

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 29
rs 5.3846
c 1
b 0
f 0
cc 8
eloc 17
nc 25
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
    /**
40
     * Appends fulfillment and rejection handlers to the promise, and returns an immutable
41
     * new promise resolving to the return value of the handler.
42
     *
43
     * @param \Closure $onFulfilled Callback to be called when promise state is fulfilled.
44
     * @param \Closure $onRejected Callback to be called when promise state is rejected.
45
     * @return Promise
46
     */
47
    public function then($onFulfilled = null, $onRejected = null)
48
    {
49
        if ($this->state === self::STATE_PENDING) {
50
            $q = new Promise();
51
            $this->context[] = [$q, $onFulfilled, $onRejected];
52
53
            return $q;
54
        }
55
56
        if ($this->state === self::STATE_FULFILLED) {
57
            return $onFulfilled
0 ignored issues
show
Bug Compatibility introduced by
The expression $onFulfilled ? (new \Gan...romise($this->current); of type Gandung\Promise\Promise|...romise\FulfilledPromise adds the type Gandung\Promise\FulfilledPromise to the return on line 57 which is incompatible with the return type declared by the interface Gandung\Promise\PromiseInterface::then of type Gandung\Promise\Promise.
Loading history...
58
                ? (new FulfilledPromise($this->current))->then($onFulfilled, null)
59
                : new FulfilledPromise($this->current);
60
        }
61
62
        return $onRejected
0 ignored issues
show
Bug Compatibility introduced by
The expression $onRejected ? (new \Gand...romise($this->current); of type Gandung\Promise\Promise|...Promise\RejectedPromise adds the type Gandung\Promise\RejectedPromise to the return on line 62 which is incompatible with the return type declared by the interface Gandung\Promise\PromiseInterface::then of type Gandung\Promise\Promise.
Loading history...
63
            ? (new RejectedPromise($this->current))->then(null, $onRejected)
64
            : new RejectedPromise($this->current);
65
    }
66
67
    /**
68
     * Set waiting callback.
69
     *
70
     * @param \Closure $callback Callback to be set.
71
     * @return void
72
     */
73
    public function setWaitCallback(\Closure $callback = null)
74
    {
75
        if (!($callback instanceof \Closure)) {
76
            throw new \InvalidArgumentException(
77
                sprintf("Parameter 1 of %s must be a valid callback.", __METHOD__)
78
            );
79
        }
80
81
        $this->waitCallback = $callback;
82
    }
83
84
    /**
85
     * Set cancellation callback.
86
     *
87
     * @param \Closure $callback Callback to be set.
88
     * @return void
89
     */
90
    public function setCancelCallback(\Closure $callback = null)
91
    {
92
        if (!($callback instanceof \Closure)) {
93
            throw new \InvalidArgumentException(
94
                sprintf("Parameter 1 of %s must be a valid callback.", __METHOD__)
95
            );
96
        }
97
98
        $this->cancelCallback = $callback;
99
    }
100
101
    /**
102
     * Comparing current promise state to new state and current promise value
103
     * to new value.
104
     *
105
     * @param mixed $value Value to be compare.
106
     * @param integer $state New promise state to be compare.
107
     * @return null|boolean
108
     */
109
    private function validateState($value, $state)
110
    {
111
        if ($this->state !== self::STATE_PENDING) {
112
            if ($value === $this->current && $state === $this->state) {
113
                return;
114
            }
115
116
            $prevStatus = $this->state;
117
            $currentStatus = $state;
118
119
            throw $this->state === $state
120
                ? new \LogicException(
121
                    sprintf("State of the promise is already %s", $prevStatus == 4 ? 'fulfilled' : 'rejected')
122
                )
123
                : new \LogicException(
124
                    sprintf(
125
                        "Unable to change %s promise to %s",
126
                        $prevStatus == 4 ? 'fulfilled' : 'rejected',
127
                        $currentStatus == 4 ? 'fulfilled' : 'rejected'
128
                    )
129
                );
130
        }
131
132
        if ($value === $this) {
133
            throw new \LogicException(
134
                "Unable to fulfill or reject a promise with itself."
135
            );
136
        }
137
138
        return true;
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144
    public function resolve($value)
145
    {
146
        $this->isStateTransformed = $this->validateState($value, self::STATE_FULFILLED);
147
148
        if ($this->isStateTransformed) {
149
            $this->setState(self::STATE_FULFILLED);
150
            $this->trigger($value);
151
        }
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157
    public function reject($reason)
158
    {
159
        $this->isStateTransformed = $this->validateState($reason, self::STATE_REJECTED);
160
161
        if ($this->isStateTransformed) {
162
            $this->setState(self::STATE_REJECTED);
163
            $this->trigger($reason);
164
        }
165
    }
166
167
    /**
168
     * Synchronously forces promise to complete using wait method.
169
     *
170
     * @return mixed
171
     */
172
    public function wait()
173
    {
174
        $this->waitInPendingState();
175
176
        $q = $this->current instanceof PromiseInterface
177
            ? $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...
178
            : $this->current;
179
180
        if ($this->current instanceof PromiseInterface || $this->currentState() === self::STATE_FULFILLED) {
181
            return $q;
182
        } else {
183
            throw $q instanceof \Exception
184
                ? $q
185
                : new \Exception($q);
186
        }
187
    }
188
189
    /**
190
     * Cancel a promise that has not yet been fulfilled.
191
     *
192
     * @return mixed
193
     */
194
    public function cancel()
195
    {
196
        if ($this->currentState() !== self::STATE_PENDING) {
197
            return;
198
        }
199
200
        if ($this->cancelCallback) {
201
            $cancelCallback = $this->cancelCallback;
202
            $this->cancelCallback = null;
203
204
            try {
205
                $cancelCallback();
206
            } catch (\Exception $e) {
207
                $this->reject($e);
208
            }
209
        }
210
211
        if ($this->currentState() === self::STATE_PENDING) {
212
            $this->reject(new \Exception('Promise has been cancelled.'));
213
        }
214
215
        $e = $this->current instanceof PromiseInterface
216
            ? $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...
217
            : $this->current;
218
219
        if ($this->current instanceof PromiseInterface || $this->currentState() === self::STATE_REJECTED) {
220
            return $e;
221
        }
222
    }
223
224
    /**
225
     * {@inheritdoc}
226
     */
227
    public function currentState()
228
    {
229
        return $this->state;
230
    }
231
232
    /**
233
     * Invoking promise handler based on current state and given value.
234
     *
235
     * @param mixed $value
236
     * @return void
237
     */
238
    private function trigger($value)
239
    {
240
        $context = $this->context;
241
        $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...
242
        $this->current = $value;
243
        $this->waitCallback = null;
244
        $this->cancelCallback = null;
245
246
        if (!method_exists($value, 'then')) {
247
            $index = $this->state === self::STATE_FULFILLED ? 1 : 2;
248
249
            Context\ContextStack::create(new TaskQueue)->store(
250
            static function () use ($index, $context, $value) {
251
                foreach ($context as $c) {
252
                    self::invokeContext($c, $index, $value);
253
                }
254
            });
255
256
            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...
257
        } elseif ($value instanceof Promise && $value->currentState() === self::STATE_PENDING) {
258
            $value->context = array_merge($value->context, $context);
259
        } else {
260
            $value->then(
261
            static function ($value) use ($context) {
262
                foreach ($context as $c) {
263
                    self::invokeContext($c, 1, $value);
264
                }
265
            },
266
            static function ($reason) use ($context) {
267
                foreach ($context as $c) {
268
                    self::invokeContext($c, 2, $reason);
269
                }
270
            }
271
            );
272
        }
273
    }
274
275
    /**
276
     * Invoking handler based on given context, index, and value.
277
     *
278
     * @param array $context
279
     * @param integer $index
280
     * @param mixed $value
281
     * @return void
282
     */
283
    private static function invokeContext($context, $index, $value)
284
    {
285
        $promise = $context[0];
286
287
        if ($promise->currentState() !== self::STATE_PENDING) {
288
            return;
289
        }
290
291
        try {
292
            if (isset($context[$index])) {
293
                $promise->resolve($context[$index]($value));
294
            } elseif ($index === 1) {
295
                $promise->resolve($value);
296
            } else {
297
                $promise->reject($value);
298
            }
299
        } 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...
300
            $promise->reject($e);
301
        } catch (\Exception $e) {
302
            $promise->reject($e);
303
        }
304
    }
305
306
    /**
307
     * Set promise state to given state.
308
     *
309
     * @param integer $state
310
     * @return void
311
     */
312
    private function setState($state)
313
    {
314
        if ($state !== self::STATE_PENDING &&
315
            $state !== self::STATE_FULFILLED &&
316
            $state !== self::STATE_REJECTED) {
317
            throw new \InvalidArgumentException(
318
                sprintf("Parameter 1 of %s must be a valid promise state.", __METHOD__)
319
            );
320
        }
321
322
        $this->state = $state;
323
    }
324
325
    /**
326
     * Synchronously forces promise to wait when current promise state is pending.
327
     *
328
     * @return void
329
     */
330
    private function waitInPendingState()
331
    {
332
        if ($this->currentState() !== self::STATE_PENDING) {
333
            return;
334
        } elseif ($this->waitCallback) {
335
            try {
336
                $waitCallback = $this->waitCallback;
337
                $this->waitCallback = null;
338
                $waitCallback();
339
            } catch (\Exception $e) {
340
                if ($this->currentState() === self::STATE_PENDING) {
341
                    $this->reject($e);
342
                } else {
343
                    throw $e;
344
                }
345
            }
346
        }
347
348
        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...
349
350
        if ($this->currentState() === self::STATE_PENDING) {
351
            $this->reject(
352
                'Invoking the synchronous wait callback resolver didn\'t resolve the current promise'
353
            );
354
        }
355
    }
356
}
357