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 ( 32c55b...8953ec )
by
unknown
06:37
created

Promise   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 281
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 56
lcom 1
cbo 4
dl 0
loc 281
rs 6.5957
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
B then() 0 19 5
A setWaitCallback() 0 10 2
A setCancelCallback() 0 10 2
B validateState() 0 13 5
A resolve() 0 6 1
A reject() 0 6 1
C wait() 0 48 10
D cancel() 0 43 10
A currentState() 0 4 1
D trigger() 0 46 10
B invokeContext() 0 20 5
A setState() 0 12 4

How to fix   Complexity   

Complex Class

Complex classes like Promise often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Promise, and based on these observations, apply Extract Interface, too.

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 integer
26
     */
27
    private $prevState;
28
29
    /**
30
     * @var \Closure
31
     */
32
    private $waitCallback;
33
34
    /**
35
     * @var \Closure
36
     */
37
    private $cancelCallback;
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)
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)
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()
82
    {
83
        if ($this->state !== self::STATE_PENDING) {
84
            $prevStatus = $this->prevState === 4 ? 'fulfilled' : 'rejected';
85
            $currentStatus = $this->currentState() === 4 ? 'fulfilled' : 'rejected';
86
87
            throw $this->currentState() === $this->prevState
88
                ? new \LogicException(sprintf("State of the promise is already %s", $prevStatus))
89
                : new \LogicException(
90
                    sprintf("Unable to change %s promise to %s", $prevStatus, $currentStatus)
91
                );
92
        }
93
    }
94
95
    public function resolve($value)
96
    {
97
        $this->validateState();
98
        $this->setState(self::STATE_FULFILLED);
99
        $this->trigger($value);
100
    }
101
102
    public function reject($reason)
103
    {
104
        $this->validateState();
105
        $this->setState(self::STATE_REJECTED);
106
        $this->trigger($reason);
107
    }
108
109
    public function wait($callback = null)
110
    {
111
        if ($this->waitCallback === null) {
112
            if (!($callback instanceof \Closure)) {
113
                throw new \LogicException(
114
                    sprintf(
115
                        "Default waiting callback resolver is not set. " .
116
                        "Synchronous wait mechanism is impossible to invoked because %s is called " .
117
                        "without callback resolver.", __METHOD__
118
                    )
119
                );
120
            }
121
122
            $this->waitCallback = $callback;
123
        }
124
125
        if ($this->currentState() !== self::STATE_PENDING) {
126
            return;
127
        }
128
129
        try {
130
            $waitCallback = $this->waitCallback;
131
            $this->waitCallback = null;
132
            $waitCallback();
133
        } catch (\Exception $e) {
134
            if ($this->currentState() === self::STATE_PENDING) {
135
                $this->reject($e);
136
            } else {
137
                throw $e;
138
            }
139
        }
140
141
        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...
142
143
        if ($this->currentState() === self::STATE_PENDING) {
144
            $this->reject(
145
                'Invoking the synchronous wait callback resolver didn\'t resolve the current promise'
146
            );
147
        }
148
149
        $q = $this->current instanceof PromiseInterface
150
            ? $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...
151
            : $this->current;
152
153
        if ($this->current instanceof PromiseInterface || $this->currentState() === self::STATE_FULFILLED) {
154
            return $q;
155
        }
156
    }
157
158
    public function cancel($callback = null)
159
    {
160
        if ($this->cancelCallback === null) {
161
            if (!($callback instanceof \Closure)) {
162
                throw new \LogicException(
163
                    sprintf(
164
                        "Default cancellation callback resolver is not set. " .
165
                        "Cancellation mechanism is impossible to invoked because %s is called " .
166
                        "without callback resolver.", __METHOD__
167
                    )
168
                );
169
            }
170
171
            $this->cancelCallback = $callback;
172
        }
173
174
        if ($this->currentState() !== self::STATE_PENDING) {
175
            return;
176
        }
177
178
        if ($this->cancelCallback) {
179
            $cancelCallback = $this->cancelCallback;
180
            $this->cancelCallback = null;
181
182
            try {
183
                $cancelCallback();
184
            } catch (\Exception $e) {
185
                $this->reject($e);
186
            }
187
        }
188
189
        if ($this->currentState() === self::STATE_PENDING) {
190
            $this->reject(new \Exception('Promise has been cancelled.'));
191
        }
192
193
        $e = $this->current instanceof PromiseInterface
194
            ? $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...
195
            : $this->current;
196
197
        if ($this->current instanceof PromiseInterface || $this->currentState() === self::STATE_REJECTED) {
198
            return $e;
199
        }
200
    }
201
202
    public function currentState()
203
    {
204
        return $this->state;
205
    }
206
207
    private function trigger($value)
208
    {
209
        if ($this->state === self::STATE_PENDING) {
210
            throw new \LogicException(
211
                "Cannot resolving or rejecting promise when in pending state."
212
            );
213
        }
214
215
        if ($value === $this->current && $this->prevState === $this->state) {
216
            return;
217
        }
218
219
        $this->prevState = $this->state;
220
221
        $context = $this->context;
222
        $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...
223
        $this->current = $value;
224
        
225
        if (!method_exists($value, 'then')) {
226
            $index = $this->state === self::STATE_FULFILLED ? 1 : 2;
227
228
            Context\ContextStack::create(new TaskQueue)->store(
229
            static function () use ($index, $context, $value) {
230
                foreach ($context as $c) {
231
                    self::invokeContext($c, $index, $value);
232
                }
233
            });
234
235
            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...
236
        } elseif ($value instanceof Promise) {
237
            $value->context = array_merge($value->context, $context);
238
        } else {
239
            $value->then(
240
            static function ($value) use ($context) {
241
                foreach ($context as $c) {
242
                    self::invokeContext($c, 1, $value);
243
                }
244
            },
245
            static function ($reason) use ($context) {
246
                foreach ($context as $c) {
247
                    self::invokeContext($c, 2, $reason);
248
                }
249
            }
250
            );
251
        }
252
    }
253
254
    private static function invokeContext($context, $index, $value)
255
    {
256
        $promise = $context[0];
257
258
        if ($promise->currentState() !== self::STATE_PENDING) {
259
            return;
260
        }
261
262
        try {
263
            if (isset($context[$index])) {
264
                $promise->resolve($context[$index]($value));
265
            } elseif ($index === 1) {
266
                $promise->resolve($value);
267
            } else {
268
                $promise->reject($value);
269
            }
270
        } catch (\Exception $e) {
271
            $promise->reject($e);
272
        }
273
    }
274
275
    private function setState($state)
276
    {
277
        if ($state !== self::STATE_PENDING &&
278
            $state !== self::STATE_FULFILLED &&
279
            $state !== self::STATE_REJECTED) {
280
            throw new \InvalidArgumentException(
281
                sprintf("Parameter 1 of %s must be a valid promise state.", __METHOD__)
282
            );
283
        }
284
285
        $this->state = $state;
286
    }
287
}
288