Completed
Push — master ( cc628c...ce99fc )
by ARCANEDEV
09:04
created

HttpClient::encode()   D

Complexity

Conditions 10
Paths 24

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 10

Importance

Changes 0
Metric Value
cc 10
eloc 14
nc 24
nop 2
dl 0
loc 26
ccs 13
cts 13
cp 1
crap 10
rs 4.8196
c 0
b 0
f 0

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\HttpClient as HttpClientContract;
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 HttpClientContract
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 4
    private function __construct()
90
    {
91 4
        $this->headers  = new HeaderBag;
92 4
        $this->options  = new CurlOptions;
93 4
        $this->response = null;
94 4
    }
95
96
    /**
97
     * Destroy the instance.
98
     */
99 2
    public function __destruct()
100
    {
101 2
        $this->close();
102 2
    }
103
104
    /* ------------------------------------------------------------------------------------------------
105
     |  Getters & Setters
106
     | ------------------------------------------------------------------------------------------------
107
     */
108
    /**
109
     * Set API Key.
110
     *
111
     * @param  string  $apiKey
112
     *
113
     * @return self
114
     */
115 298
    public function setApiKey($apiKey)
116
    {
117 298
        $this->apiKey = $apiKey;
118
119 298
        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 2
    public function getTimeout()
142
    {
143 2
        return $this->timeout;
144
    }
145
146
    /**
147
     * Set the timeout.
148
     *
149
     * @param  int  $seconds
150
     *
151
     * @return self
152
     */
153 688
    public function setTimeout($seconds)
154
    {
155 688
        $this->timeout = (int) max($seconds, 0);
156
157 688
        return $this;
158
    }
159
160
    /**
161
     * Get the connect timeout.
162
     *
163
     * @return int
164
     */
165 2
    public function getConnectTimeout()
166
    {
167 2
        return $this->connectTimeout;
168
    }
169
170
    /**
171
     * Set the connect timeout.
172
     *
173
     * @param  int  $seconds
174
     *
175
     * @return self
176
     */
177 2
    public function setConnectTimeout($seconds)
178
    {
179 2
        $this->connectTimeout = (int) max($seconds, 0);
180
181 2
        return $this;
182
    }
183
184
    /**
185
     * Get array options.
186
     *
187
     * @return array
188
     */
189 300
    public function getOptions()
190
    {
191 300
        return $this->options->get();
192
    }
193
194
    /**
195
     * Set array options.
196
     *
197
     * @param  array  $options
198
     *
199
     * @return self
200
     */
201 300
    public function setOptionArray(array $options)
202
    {
203 300
        $this->options->setOptions($options);
204
205 300
        return $this;
206
    }
207
208
    /* ------------------------------------------------------------------------------------------------
209
     |  Curl Functions
210
     | ------------------------------------------------------------------------------------------------
211
     */
212
    /**
213
     * Init curl.
214
     */
215 298
    private function init()
216
    {
217 298
        $this->curl = curl_init();
218 298
    }
219
220
    /**
221
     * Execute curl.
222
     */
223 298
    private function execute()
224
    {
225 298
        curl_setopt_array($this->curl, $this->getOptions());
226 298
        $this->response     = curl_exec($this->curl);
227 298
        $this->errorCode    = curl_errno($this->curl);
228 298
        $this->errorMessage = curl_error($this->curl);
229 298
    }
230
231
    /**
232
     * Close curl.
233
     */
234 300
    private function close()
235
    {
236 300
        if (is_resource($this->curl))
237 298
            curl_close($this->curl);
238 300
    }
239
240
    /* ------------------------------------------------------------------------------------------------
241
     |  Main Functions
242
     | ------------------------------------------------------------------------------------------------
243
     */
244
    /**
245
     * Make the HTTP Client with options.
246
     *
247
     * @param  array  $options
248
     *
249
     * @return static
250
     */
251 2
    public static function make(array $options = [])
252
    {
253 2
        return (new static)->setOptionArray($options);
254
    }
255
256
    /**
257
     * Get the HTTP.
258
     *
259
     * @return self
260
     */
261 688
    public static function instance()
262
    {
263 688
        if ( ! self::$instance)
264 2
            self::$instance = new self;
265
266 688
        return self::$instance;
267
    }
268
269
    /**
270
     * Curl the request.
271
     *
272
     * @param  string        $method
273
     * @param  string        $url
274
     * @param  array|string  $params
275
     * @param  array         $headers
276
     * @param  bool          $hasFile
277
     *
278
     * @return array
279
     */
280 298
    public function request($method, $url, $params, $headers, $hasFile)
281
    {
282 298
        if ($method !== 'post')
283 212
            $url    = str_parse_url($url, $params);
284
        else
285 252
            $params = $hasFile ? $params : self::encode($params);
286
287 298
        $this->headers->prepare($this->apiKey, $headers, $hasFile);
288 298
        $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...
289 298
        $this->setOptionArray([
290 298
            CURLOPT_CONNECTTIMEOUT => $this->connectTimeout,
291 298
            CURLOPT_TIMEOUT        => $this->timeout,
292
        ]);
293
294 298
        $respHeaders = [];
295 298
        $this->prepareResponseHeaders($respHeaders);
296
297 298
        $this->init();
298 298
        $this->execute();
299 298
        $this->checkCertErrors();
300 298
        $this->checkResponse();
301
302 298
        $statusCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
303 298
        $this->close();
304
305 298
        return [$this->response, $statusCode, $respHeaders];
306
    }
307
308
    /**
309
     * Check Cert Errors.
310
     */
311 298
    private function checkCertErrors()
312
    {
313 298
        if (SslChecker::hasCertErrors($this->errorCode)) {
314
            $this->headers->set(
315
                'X-Stripe-Client-Info',
316
                '{"ca":"using Stripe-supplied CA bundle"}'
317
            );
318
319
            $this->setOptionArray([
320
                CURLOPT_HTTPHEADER => $this->headers->get(),
321
                CURLOPT_CAINFO     => SslChecker::caBundle()
322
            ]);
323
324
            $this->execute();
325
        }
326 298
    }
327
328
    /**
329
     * Encode array to query string
330
     *
331
     * @param  array|string  $array
332
     * @param  string|null   $prefix
333
     *
334
     * @return string
335
     */
336 248
    protected static function encode($array, $prefix = null)
337
    {
338
        // @codeCoverageIgnoreStart
339
        if ( ! is_array($array)) return $array;
340
        // @codeCoverageIgnoreEnd
341
342 248
        $result = [];
343
344 248
        foreach ($array as $key => $value) {
345 244
            if (is_null($value)) continue;
346
347 244
            if ($prefix)
348 112
                $key = ($key !== null && (! is_int($key) || is_array($value)))
349 112
                    ? "{$prefix}[{$key}]"
350 112
                    : "{$prefix}[]";
351
352 244
            if ( ! is_array($value)) {
353 244
                $result[] = urlencode($key) . '=' . urlencode($value);
354
            }
355 112
            elseif ($enc = self::encode($value, $key)) {
356 244
                $result[] = $enc;
357
            }
358
        }
359
360 248
        return implode('&', $result);
361
    }
362
363
    /* ------------------------------------------------------------------------------------------------
364
     |  Other Functions
365
     | ------------------------------------------------------------------------------------------------
366
     */
367
    /**
368
     * Check Response.
369
     *
370
     * @throws \Arcanedev\Stripe\Exceptions\ApiConnectionException
371
     */
372 298
    private function checkResponse()
373
    {
374 298
        if ($this->response !== false) return;
375
376
        $this->close();
377
        $this->handleCurlError();
378
    }
379
380
    /**
381
     * Handle CURL errors.
382
     *
383
     * @throws \Arcanedev\Stripe\Exceptions\ApiConnectionException
384
     */
385
    private function handleCurlError()
386
    {
387
        switch ($this->errorCode) {
388
            case CURLE_COULDNT_CONNECT:
389
            case CURLE_COULDNT_RESOLVE_HOST:
390
            case CURLE_OPERATION_TIMEOUTED:
391
                $msg = "Could not connect to Stripe ({$this->apiBaseUrl}). Please check your internet connection "
392
                    . 'and try again.  If this problem persists, you should check Stripe\'s service status at '
393
                    . 'https://twitter.com/stripestatus, or';
394
                break;
395
396
            case CURLE_SSL_CACERT:
397
            case CURLE_SSL_PEER_CERTIFICATE:
398
                $msg = 'Could not verify Stripe\'s SSL certificate.  Please make sure that your network is not '
399
                    . "intercepting certificates. (Try going to {$this->apiBaseUrl} in your browser.) "
400
                    . 'If this problem persists,';
401
                break;
402
403
            default:
404
                $msg = 'Unexpected error communicating with Stripe. If this problem persists,';
405
                // no break
406
        }
407
408
        $msg .= ' let us know at [email protected].';
409
410
        $msg .= "\n\n(Network error [errno {$this->errorCode}]: {$this->errorMessage})";
411
412
        throw new ApiConnectionException($msg);
413
    }
414
415
    /**
416
     * Prepare Response Headers.
417
     *
418
     * @param  array  $respHeaders
419
     */
420
    private function prepareResponseHeaders(array &$respHeaders)
421
    {
422 298
        $this->options->setOption(CURLOPT_HEADERFUNCTION, function ($curl, $header_line) use (&$respHeaders) {
423
            // Ignore the HTTP request line (HTTP/1.1 200 OK)
424 298
            if (strpos($header_line, ":") === false)
425 298
                return strlen($header_line);
426
427 298
            list($key, $value) = explode(":", trim($header_line), 2);
428 298
            $respHeaders[trim($key)] = trim($value);
429
430 298
            return strlen($header_line);
431 298
        });
432 298
    }
433
}
434