Client::push()   F
last analyzed

Complexity

Conditions 18
Paths 460

Size

Total Lines 89

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
nc 460
nop 0
dl 0
loc 89
rs 1.4
c 0
b 0
f 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 resource
66
     */
67
    private $curlMultiHandle;
68
	
69
    /**
70
     * options for curl
71
     *
72
     * @var resource
73
     */
74
    private $curlOptions = array();
75
76
    /**
77
     * Client constructor.
78
     *
79
     * @param AuthProviderInterface $authProvider
80
     * @param bool $isProductionEnv
81
     */
82
    public function __construct(AuthProviderInterface $authProvider, bool $isProductionEnv = false, array $curlOptions = array())
83
    {
84
        $this->authProvider = $authProvider;
85
        $this->isProductionEnv = $isProductionEnv;
86
		$this->curlOptions = $curlOptions; 
0 ignored issues
show
Documentation Bug introduced by
It seems like $curlOptions of type array is incompatible with the declared type resource of property $curlOptions.

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...
87
    }
88
89
    /**
90
     * Push notifications to APNs.
91
     *
92
     * @return ApnsResponseInterface[]
93
     */
94
    public function push(): array
95
    {
96
        $responseCollection = [];
97
98
        if (!$this->curlMultiHandle) {
99
            $this->curlMultiHandle = curl_multi_init();
100
101
            if (!defined('CURLPIPE_MULTIPLEX')) {
102
                define('CURLPIPE_MULTIPLEX', 2);
103
            }
104
105
            curl_multi_setopt($this->curlMultiHandle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
106
            if (defined('CURLMOPT_MAX_HOST_CONNECTIONS')) {
107
                curl_multi_setopt($this->curlMultiHandle, CURLMOPT_MAX_HOST_CONNECTIONS, $this->maxConcurrentConnections);
108
            }
109
        }
110
111
        $mh = $this->curlMultiHandle;
112
113
        $i = 0;
114
        while (!empty($this->notifications) && $i++ < $this->nbConcurrentRequests) {
115
            $notification = array_pop($this->notifications);
116
            curl_multi_add_handle($mh, $this->prepareHandle($notification));
117
        }
118
119
        // Clear out curl handle buffer
120
        do {
121
            $execrun = curl_multi_exec($mh, $running);
122
        } while ($execrun === CURLM_CALL_MULTI_PERFORM);
123
124
        // Continue processing while we have active curl handles
125
        while ($running > 0 && $execrun === CURLM_OK) {
126
            // Block until data is available
127
            $select_fd = curl_multi_select($mh);
128
            // If select returns -1 while running, wait 250 microseconds before continuing
129
            // Using curl_multi_timeout would be better but it isn't available in PHP yet
130
            // https://php.net/manual/en/function.curl-multi-select.php#115381
131
            if ($running && $select_fd === -1) {
132
                usleep(250);
133
            }
134
135
            // Continue to wait for more data if needed
136
            do {
137
                $execrun = curl_multi_exec($mh, $running);
138
            } while ($execrun === CURLM_CALL_MULTI_PERFORM);
139
140
            // Start reading results
141
            while ($done = curl_multi_info_read($mh)) {
142
                $handle = $done['handle'];
143
144
                $result = curl_multi_getcontent($handle);
145
146
                // find out which token the response is about
147
                $token = curl_getinfo($handle, CURLINFO_PRIVATE);
148
149
                $responseParts = explode("\r\n\r\n", $result, 2);
150
                $headers = '';
151
                $body = '';
152
                if (isset($responseParts[0])) {
153
                    $headers = $responseParts[0];
154
                }
155
                if (isset($responseParts[1])) {
156
                    $body = $responseParts[1];
157
                }
158
159
                $statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
160
                if ($statusCode === 0) {
161
                    throw new \Exception(curl_error($handle));
162
                }
163
164
                $responseCollection[] = new Response($statusCode, $headers, (string)$body, $token);
165
                curl_multi_remove_handle($mh, $handle);
166
                curl_close($handle);
167
168
                if (!empty($this->notifications)) {
169
                    $notification = array_pop($this->notifications);
170
                    curl_multi_add_handle($mh, $this->prepareHandle($notification));
171
                    $running++;
172
                }
173
            }
174
        }
175
176
        if ($this->autoCloseConnections) {
177
            curl_multi_close($mh);
178
            $this->curlMultiHandle = null;
179
        }
180
181
        return $responseCollection;
182
    }
183
184
    /**
185
     * Prepares a curl handle from a Notification object.
186
     *
187
     * @param Notification $notification
188
     *
189
     * @return resource Curl resource
190
     */
191
    private function prepareHandle(Notification $notification)
192
    {
193
        $request = new Request($notification, $this->isProductionEnv);
194
        $ch = curl_init();
195
196
        $this->authProvider->authenticateClient($request);
197
198
        curl_setopt_array($ch, $this->curlOptions + $request->getOptions());
199
        curl_setopt($ch, CURLOPT_HTTPHEADER, $request->getDecoratedHeaders());
200
201
        // store device token to identify response
202
        curl_setopt($ch, CURLOPT_PRIVATE, $notification->getDeviceToken());
203
204
        return $ch;
205
    }
206
207
    /**
208
     * Add several notifications in queue for sending.
209
     *
210
     * @param Notification[] $notifications
211
     */
212
    public function addNotifications(array $notifications)
213
    {
214
        foreach ($notifications as $notification) {
215
            if (in_array($notification, $this->notifications, true)) {
216
                continue;
217
            }
218
219
            $this->addNotification($notification);
220
        }
221
    }
222
223
    /**
224
     * Add notification in queue for sending.
225
     *
226
     * @param Notification $notification
227
     */
228
    public function addNotification(Notification $notification)
229
    {
230
        $this->notifications[] = $notification;
231
    }
232
233
    /**
234
     * Get already added notifications.
235
     *
236
     * @return Notification[]
237
     */
238
    public function getNotifications(): array
239
    {
240
        return $this->notifications;
241
    }
242
243
    /**
244
     * Close the current curl multi handle.
245
     */
246
    public function close()
247
    {
248
        if ($this->curlMultiHandle) {
249
            curl_multi_close($this->curlMultiHandle);
250
            $this->curlMultiHandle = null;
251
        }
252
    }
253
254
    /**
255
     * Set the number of concurrent requests sent through the multiplexed connections.
256
     *
257
     * @param int $nbConcurrentRequests
258
     */
259
    public function setNbConcurrentRequests($nbConcurrentRequests)
260
    {
261
        $this->nbConcurrentRequests = $nbConcurrentRequests;
262
    }
263
264
265
    /**
266
     * Set the number of maximum concurrent connections established to the APNS servers.
267
     *
268
     * @param int $maxConcurrentConnections
269
     */
270
    public function setMaxConcurrentConnections($maxConcurrentConnections)
271
    {
272
        $this->maxConcurrentConnections = $maxConcurrentConnections;
273
    }
274
275
    /**
276
     * Set if the client should automatically close the connections or not. Apple recommends keeping
277
     * connections open if you send more than a few notification per minutes.
278
     *
279
     * @param bool $autoCloseConnections
280
     */
281
    public function setAutoCloseConnections($autoCloseConnections)
282
    {
283
        $this->autoCloseConnections = $autoCloseConnections;
284
    }
285
}
286