Completed
Push — master ( b9ac23...46f1c6 )
by ARCANEDEV
7s
created

HttpClient::encode()   D

Complexity

Conditions 10
Paths 24

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 10

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 27
ccs 17
cts 17
cp 1
rs 4.8196
cc 10
eloc 14
nc 24
nop 2
crap 10

How to fix   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 namespace Arcanedev\Stripe\Http\Curl;
2
3
use Arcanedev\Stripe\Contracts\Http\Curl\HttpClientInterface;
4
use Arcanedev\Stripe\Exceptions\ApiConnectionException;
5
6
/**
7
 * Class     HttpClient
8
 *
9
 * @package  Arcanedev\Stripe\Http\Curl
10
 * @author   ARCANEDEV <[email protected]>
11
 */
12
class HttpClient implements HttpClientInterface
13
{
14
    /* ------------------------------------------------------------------------------------------------
15
     |  Constants
16
     | ------------------------------------------------------------------------------------------------
17
     */
18
    const DEFAULT_TIMEOUT = 80;
19
    const DEFAULT_CONNECT_TIMEOUT = 30;
20
21
    /* ------------------------------------------------------------------------------------------------
22
     |  Properties
23
     | ------------------------------------------------------------------------------------------------
24
     */
25
    /**
26
     * The HTTP Client instance.
27
     *
28
     * @var \Arcanedev\Stripe\Http\Curl\HttpClient
29
     */
30
    private static $instance;
31
32
    /**
33
     * @var string
34
     */
35
    private $apiKey;
36
37
    /**
38
     * @var string
39
     */
40
    private $apiBaseUrl;
41
42
    /**
43
     * @var \Arcanedev\Stripe\Http\Curl\HeaderBag
44
     */
45
    private $headers;
46
47
    /**
48
     * @var \Arcanedev\Stripe\Http\Curl\CurlOptions
49
     */
50
    private $options;
51
52
    /**
53
     * @var resource
54
     */
55
    private $curl;
56
57
    /**
58
     * @var int
59
     */
60
    private $timeout = self::DEFAULT_TIMEOUT;
61
62
    /**
63
     * @var int
64
     */
65
    private $connectTimeout = self::DEFAULT_CONNECT_TIMEOUT;
66
67
    /**
68
     * @var mixed
69
     */
70
    private $response;
71
72
    /**
73
     * @var int
74
     */
75
    private $errorCode;
76
77
    /**
78
     * @var string
79
     */
80
    private $errorMessage;
81
82
    /* ------------------------------------------------------------------------------------------------
83
     |  Constructor & Destructor
84
     | ------------------------------------------------------------------------------------------------
85
     */
86
    /**
87
     * Create a HttpClient instance.
88
     */
89 5
    private function __construct()
90
    {
91 5
        $this->headers  = new HeaderBag;
92 5
        $this->options  = new CurlOptions;
93 5
        $this->response = null;
94 5
    }
95
96
    /**
97
     * Destroy the instance.
98
     */
99
    public function __destruct()
100
    {
101
        $this->close();
102
    }
103
104
    /* ------------------------------------------------------------------------------------------------
105
     |  Getters & Setters
106
     | ------------------------------------------------------------------------------------------------
107
     */
108
    /**
109
     * Set API Key.
110
     *
111
     * @param  string  $apiKey
112
     *
113
     * @return self
114
     */
115 654
    public function setApiKey($apiKey)
116
    {
117 654
        $this->apiKey = $apiKey;
118
119 654
        return $this;
120
    }
121
122
    /**
123
     * Set Base URL.
124
     *
125
     * @param  string  $apiBaseUrl
126
     *
127
     * @return self
128
     */
129
    public function setApiBaseUrl($apiBaseUrl)
130
    {
131
        $this->apiBaseUrl = $apiBaseUrl;
132
133
        return $this;
134
    }
135
136
    /**
137
     * Get the timeout.
138
     *
139
     * @return int
140
     */
141 5
    public function getTimeout()
142
    {
143 5
        return $this->timeout;
144
    }
145
146
    /**
147
     * Set the timeout.
148
     *
149
     * @param  int  $seconds
150
     *
151
     * @return self
152
     */
153 1299
    public function setTimeout($seconds)
154
    {
155 1299
        $this->timeout = (int) max($seconds, 0);
156
157 1299
        return $this;
158
    }
159
160
    /**
161
     * Get the connect timeout.
162
     *
163
     * @return int
164
     */
165 5
    public function getConnectTimeout()
166
    {
167 5
        return $this->connectTimeout;
168
    }
169
170
    /**
171
     * Set the connect timeout.
172
     *
173
     * @param  int  $seconds
174
     *
175
     * @return self
176
     */
177 5
    public function setConnectTimeout($seconds)
178
    {
179 5
        $this->connectTimeout = (int) max($seconds, 0);
180
181 5
        return $this;
182
    }
183
184
    /**
185
     * Set array options.
186
     *
187
     * @param  array  $options
188
     *
189
     * @return self
190
     */
191 654
    public function setOptionArray(array $options)
192
    {
193 654
        curl_setopt_array($this->curl, $options);
194
195 654
        return $this;
196
    }
197
198
    /* ------------------------------------------------------------------------------------------------
199
     |  Curl Functions
200
     | ------------------------------------------------------------------------------------------------
201
     */
202
    /**
203
     * Init curl.
204
     */
205 654
    private function init()
206
    {
207 654
        $this->curl = curl_init();
208 654
    }
209
210
    /**
211
     * Execute curl.
212
     */
213 654
    private function execute()
214
    {
215 654
        $this->response     = curl_exec($this->curl);
216 654
        $this->errorCode    = curl_errno($this->curl);
217 654
        $this->errorMessage = curl_error($this->curl);
218 654
    }
219
220
    /**
221
     * Close curl.
222
     */
223 654
    private function close()
224
    {
225 654
        if (is_resource($this->curl)) {
226 654
            curl_close($this->curl);
227 523
        }
228 654
    }
229
230
    /* ------------------------------------------------------------------------------------------------
231
     |  Main Functions
232
     | ------------------------------------------------------------------------------------------------
233
     */
234
    /**
235
     * Get the HTTP.
236
     *
237
     * @return self
238
     */
239 1299
    public static function instance()
240
    {
241 1299
        if ( ! self::$instance) {
242 5
            self::$instance = new self;
243 4
        }
244
245 1299
        return self::$instance;
246
    }
247
248
    /**
249
     * Curl the request.
250
     *
251
     * @param  string        $method
252
     * @param  string        $url
253
     * @param  array|string  $params
254
     * @param  array         $headers
255
     * @param  bool          $hasFile
256
     *
257
     * @return array
258
     */
259 654
    public function request($method, $url, $params, $headers, $hasFile)
260
    {
261 654
        if ($method !== 'post') {
262 455
            $url    = str_parse_url($url, $params);
263 364
        }
264
        else {
265 554
            $params = $hasFile ? $params : self::encode($params);
266
        }
267
268 654
        $this->headers->prepare($this->apiKey, $headers, $hasFile);
269 654
        $this->options->make($method, $url, $params, $this->headers->get(), $hasFile);
0 ignored issues
show
Bug introduced by
It seems like $params can also be of type array; however, Arcanedev\Stripe\Http\Curl\CurlOptions::make() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
270 654
        $this->options->setOptions([
271 654
            CURLOPT_CONNECTTIMEOUT => $this->connectTimeout,
272 654
            CURLOPT_TIMEOUT        => $this->timeout,
273 523
        ]);
274
275 654
        $this->init();
276 654
        $this->setOptionArray($this->options->get());
277 654
        $this->execute();
278
279 654
        $this->checkCertErrors();
280 654
        $this->checkResponse();
281
282 654
        $statusCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
283 654
        $this->close();
284
285 654
        return [$this->response, $statusCode];
286
    }
287
288
    /**
289
     * Check Cert Errors.
290
     */
291 654
    private function checkCertErrors()
292
    {
293 654
        if (SslChecker::hasCertErrors($this->errorCode)) {
294
            $this->headers->set(
295
                'X-Stripe-Client-Info',
296
                '{"ca":"using Stripe-supplied CA bundle"}'
297
            );
298
299
            $this->setOptionArray([
300
                CURLOPT_HTTPHEADER => $this->headers->get(),
301
                CURLOPT_CAINFO     => SslChecker::caBundle()
302
            ]);
303
304
            $this->execute();
305
        }
306 654
    }
307
308
    /**
309
     * Encode array to query string
310
     *
311
     * @param  array|string  $array
312
     * @param  string|null   $prefix
313
     *
314
     * @return string
315
     */
316 545
    protected static function encode($array, $prefix = null)
317
    {
318
        // @codeCoverageIgnoreStart
319
        if ( ! is_array($array)) return $array;
320
        // @codeCoverageIgnoreEnd
321
322 545
        $result = [];
323
324 545
        foreach ($array as $key => $value) {
325 535
            if (is_null($value)) continue;
326
327 535
            if ($prefix) {
328 445
                $key = ($key !== null && (! is_int($key) || is_array($value)))
329 445
                    ? $prefix . "[" . $key . "]"
330 445
                    : $prefix . "[]";
331 356
            }
332
333 535
            if ( ! is_array($value)) {
334 535
                $result[] = urlencode($key) . '=' . urlencode($value);
335 428
            }
336 445
            elseif ($enc = self::encode($value, $key)) {
337 463
                $result[] = $enc;
338 356
            }
339 436
        }
340
341 545
        return implode('&', $result);
342
    }
343
344
    /* ------------------------------------------------------------------------------------------------
345
     |  Other Functions
346
     | ------------------------------------------------------------------------------------------------
347
     */
348
    /**
349
     * Check Response.
350
     *
351
     * @throws \Arcanedev\Stripe\Exceptions\ApiConnectionException
352
     */
353 654
    private function checkResponse()
354
    {
355 654
        if ($this->response !== false) return;
356
357
        $this->close();
358
        $this->handleCurlError();
359
    }
360
361
    /**
362
     * Handle CURL errors.
363
     *
364
     * @throws \Arcanedev\Stripe\Exceptions\ApiConnectionException
365
     */
366
    private function handleCurlError()
367
    {
368
        switch ($this->errorCode) {
369
            case CURLE_COULDNT_CONNECT:
370
            case CURLE_COULDNT_RESOLVE_HOST:
371
            case CURLE_OPERATION_TIMEOUTED:
372
                $msg = "Could not connect to Stripe ({$this->apiBaseUrl}). Please check your internet connection "
373
                    . 'and try again.  If this problem persists, you should check Stripe\'s service status at '
374
                    . 'https://twitter.com/stripestatus, or';
375
                break;
376
377
            case CURLE_SSL_CACERT:
378
            case CURLE_SSL_PEER_CERTIFICATE:
379
                $msg = 'Could not verify Stripe\'s SSL certificate.  Please make sure that your network is not '
380
                    . "intercepting certificates. (Try going to {$this->apiBaseUrl} in your browser.) "
381
                    . 'If this problem persists,';
382
                break;
383
384
            default:
385
                $msg = 'Unexpected error communicating with Stripe. If this problem persists,';
386
                // no break
387
        }
388
389
        $msg .= ' let us know at [email protected].';
390
391
        $msg .= "\n\n(Network error [errno {$this->errorCode}]: {$this->errorMessage})";
392
393
        throw new ApiConnectionException($msg);
394
    }
395
}
396