Completed
Pull Request — master (#48)
by
unknown
11:28
created

Client::push()   F

Complexity

Conditions 16
Paths 228

Size

Total Lines 80

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 80
rs 3.943
c 0
b 0
f 0
cc 16
nc 228
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the Pushok package.
5
 *
6
 * (c) Arthur Edamov <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Pushok;
13
14
/**
15
 * Class Client
16
 * @package Pushok
17
 */
18
class Client
19
{
20
    /**
21
     * Array of notifications.
22
     *
23
     * @var Notification[]
24
     */
25
    private $notifications = [];
26
27
    /**
28
     * Authentication provider.
29
     *
30
     * @var AuthProviderInterface
31
     */
32
    private $authProvider;
33
34
    /**
35
     * Production or sandbox environment.
36
     *
37
     * @var bool
38
     */
39
    private $isProductionEnv;
40
41
    /**
42
     * Number of concurrent requests to multiplex in the same connection.
43
     *
44
     * @var int
45
     */
46
    private $nbConcurrentRequests = 20;
47
48
    /**
49
     * Number of maximum concurrent connections established to the APNS servers.
50
     *
51
     * @var int
52
     */
53
    private $maxConcurrentConnections = 1;
54
55
    /**
56
     * Flag to know if we should automatically close connections to the APNS servers or keep them alive.
57
     *
58
     * @var bool
59
     */
60
    private $autoCloseConnections = true;
61
62
    /**
63
     * Current curl_multi handle instance.
64
     *
65
     * @var Object
66
     */
67
    private $curlMultiHandle;
68
69
    /**
70
     * Client constructor.
71
     *
72
     * @param AuthProviderInterface $authProvider
73
     * @param bool $isProductionEnv
74
     */
75
    public function __construct(AuthProviderInterface $authProvider, bool $isProductionEnv = false)
76
    {
77
        $this->authProvider = $authProvider;
78
        $this->isProductionEnv = $isProductionEnv;
79
    }
80
81
    /**
82
     * Push notifications to APNs.
83
     *
84
     * @return ApnsResponseInterface[]
85
     */
86
    public function push(): array
87
    {
88
        if (!$this->curlMultiHandle) {
89
            $this->curlMultiHandle = curl_multi_init();
0 ignored issues
show
Documentation Bug introduced by
It seems like curl_multi_init() of type resource is incompatible with the declared type object of property $curlMultiHandle.

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...
90
91
            if (!defined('CURLPIPE_MULTIPLEX')) {
92
                define('CURLPIPE_MULTIPLEX', 2);
93
            }
94
95
            curl_multi_setopt($this->curlMultiHandle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
96
            curl_multi_setopt($this->curlMultiHandle, CURLMOPT_MAX_HOST_CONNECTIONS, $this->maxConcurrentConnections);
97
        }
98
99
        $mh = $this->curlMultiHandle;
100
101
        $i = 0;
102
        while (!empty($this->notifications) && $i++ < $this->nbConcurrentRequests) {
103
            $notification = array_pop($this->notifications);
104
            curl_multi_add_handle($mh, $this->prepareHandle($notification));
105
        }
106
107
        // Clear out curl handle buffer
108
        do {
109
            $execrun = curl_multi_exec($mh, $running);
110
        } while ($execrun === CURLM_CALL_MULTI_PERFORM);
111
112
        // Continue processing while we have active curl handles
113
        while ($running > 0 && $execrun === CURLM_OK) {
114
            // Block until data is available
115
            $select_fd = curl_multi_select($mh);
116
            // If select returns -1 while running, wait 250 microseconds before continuing
117
            // Using curl_multi_timeout would be better but it isn't available in PHP yet
118
            // https://php.net/manual/en/function.curl-multi-select.php#115381
119
            if ($running && $select_fd === -1) {
120
                usleep(250);
121
            }
122
123
            // Continue to wait for more data if needed
124
            do {
125
                $execrun = curl_multi_exec($mh, $running);
126
            } while ($execrun === CURLM_CALL_MULTI_PERFORM);
127
128
            // Start reading results
129
            while ($done = curl_multi_info_read($mh)) {
130
                $handle = $done['handle'];
131
132
                $result = curl_multi_getcontent($handle);
133
134
                // find out which token the response is about
135
                $token = curl_getinfo($handle, CURLINFO_PRIVATE);
136
137
                $responseParts = explode("\r\n\r\n", $result, 2);
138
                $headers = '';
139
                $body = '';
140
                if (isset($responseParts[0])) {
141
                    $headers = $responseParts[0];
142
                }
143
                if (isset($responseParts[1])) {
144
                    $body = $responseParts[1];
145
                }
146
147
                $statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
148
                $responseCollection[] = new Response($statusCode, $headers, $body, $token);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$responseCollection was never initialized. Although not strictly required by PHP, it is generally a good practice to add $responseCollection = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
149
                curl_multi_remove_handle($mh, $handle);
150
                curl_close($handle);
151
152
                if (!empty($this->notifications)) {
153
                    $notification = array_pop($this->notifications);
154
                    curl_multi_add_handle($mh, $this->prepareHandle($notification));
155
                }
156
            }
157
        }
158
159
        if ($this->autoCloseConnections) {
160
            curl_multi_close($mh);
161
            $this->curlMultiHandle = null;
162
        }
163
164
        return $responseCollection;
0 ignored issues
show
Bug introduced by
The variable $responseCollection does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
165
    }
166
167
    /**
168
     * Prepares a curl handle from a Notification object.
169
     *
170
     * @param Notification $notification
171
     */
172
    private function prepareHandle(Notification $notification)
173
    {
174
        $request = new Request($notification, $this->isProductionEnv);
175
        $ch = curl_init();
176
177
        $this->authProvider->authenticateClient($request);
178
179
        curl_setopt_array($ch, $request->getOptions());
180
        curl_setopt($ch, CURLOPT_HTTPHEADER, $request->getDecoratedHeaders());
181
182
        // store device token to identify response
183
        curl_setopt($ch, CURLOPT_PRIVATE, $notification->getDeviceToken());
184
185
        return $ch;
186
    }
187
188
    /**
189
     * Add notification in queue for sending.
190
     *
191
     * @param Notification $notification
192
     */
193
    public function addNotification(Notification $notification)
194
    {
195
        $this->notifications[] = $notification;
196
    }
197
198
    /**
199
     * Add several notifications in queue for sending.
200
     *
201
     * @param Notification[] $notifications
202
     */
203
    public function addNotifications(array $notifications)
204
    {
205
        foreach ($notifications as $notification) {
206
            if (in_array($notification, $this->notifications, true)) {
207
                continue;
208
            }
209
210
            $this->addNotification($notification);
211
        }
212
    }
213
214
    /**
215
     * Get already added notifications.
216
     *
217
     * @return Notification[]
218
     */
219
    public function getNotifications(): array
220
    {
221
        return $this->notifications;
222
    }
223
224
    /**
225
     * Close the current curl multi handle.
226
     */
227
    public function close()
228
    {
229
        if ($this->curlMultiHandle) {
230
            curl_multi_close($this->curlMultiHandle);
231
            $this->curlMultiHandle = null;
232
        }
233
    }
234
235
    /**
236
     * Set the number of concurrent requests sent through the multiplexed connections.
237
     *
238
     * @param int $nbConcurrentRequests
239
     */
240
    public function setNbConcurrentRequests($nbConcurrentRequests)
241
    {
242
        $this->nbConcurrentRequests = $nbConcurrentRequests;
243
    }
244
245
246
    /**
247
     * Set the number of maximum concurrent connections established to the APNS servers.
248
     *
249
     * @param int $nbConcurrentRequests
0 ignored issues
show
Bug introduced by
There is no parameter named $nbConcurrentRequests. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
250
     */
251
    public function setMaxConcurrentConnections($maxConcurrentConnections)
252
    {
253
        $this->maxConcurrentConnections = $maxConcurrentConnections;
254
    }
255
256
    /**
257
     * Set wether or not the client should automatically close the connections. Apple recommends keeping
258
     * connections open if you send more than a few notification per minutes.
259
     *
260
     * @param bool $nbConcurrentRequests
0 ignored issues
show
Bug introduced by
There is no parameter named $nbConcurrentRequests. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
261
     */
262
    public function setAutoCloseConnections($autoCloseConnections)
263
    {
264
        $this->autoCloseConnections = $autoCloseConnections;
265
    }
266
}
267