Completed
Pull Request — master (#13)
by ARCANEDEV
27:17 queued 10:33
created

HttpClient::processResourceParams()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 18
ccs 9
cts 9
cp 1
rs 9.2
cc 4
eloc 8
nc 4
nop 1
crap 4
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\Utilities\Request
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
    private function __construct()
90
    {
91 5
        $this->headers  = new HeaderBag;
92
        $this->options  = new CurlOptions;
93 5
        $this->response = null;
94 5
    }
95 5
96 5
    /**
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
    public function setApiKey($apiKey)
116
    {
117 644
        $this->apiKey = $apiKey;
118
119 644
        return $this;
120
    }
121 644
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
    public function getTimeout()
142
    {
143 5
        return $this->timeout;
144
    }
145 5
146
    /**
147
     * Set the timeout.
148
     *
149
     * @param  int  $seconds
150
     *
151
     * @return self
152
     */
153
    public function setTimeout($seconds)
154
    {
155 1289
        $this->timeout = (int) max($seconds, 0);
156
157 1289
        return $this;
158
    }
159 1289
160
    /**
161
     * Get the connect timeout.
162
     *
163
     * @return int
164
     */
165
    public function getConnectTimeout()
166
    {
167 5
        return $this->connectTimeout;
168
    }
169 5
170
    /**
171
     * Set the connect timeout.
172
     *
173
     * @param  int  $seconds
174
     *
175
     * @return self
176
     */
177
    public function setConnectTimeout($seconds)
178
    {
179 5
        $this->connectTimeout = (int) max($seconds, 0);
180
181 5
        return $this;
182
    }
183 5
184
    /**
185
     * Set array options.
186
     *
187
     * @param  array  $options
188
     *
189
     * @return self
190
     */
191
    public function setOptionArray(array $options)
192
    {
193 644
        curl_setopt_array($this->curl, $options);
194
195 644
        return $this;
196
    }
197 644
198
    /* ------------------------------------------------------------------------------------------------
199
     |  Curl Functions
200
     | ------------------------------------------------------------------------------------------------
201
     */
202
    /**
203
     * Init curl.
204
     */
205
    private function init()
206
    {
207 644
        $this->curl = curl_init();
208
    }
209 644
210 644
    /**
211
     * Execute curl.
212
     */
213
    private function execute()
214
    {
215 644
        $this->response     = curl_exec($this->curl);
216
        $this->errorCode    = curl_errno($this->curl);
217 644
        $this->errorMessage = curl_error($this->curl);
218 644
    }
219 644
220 644
    /**
221
     * Close curl.
222
     */
223
    private function close()
224
    {
225 644
        if (is_resource($this->curl)) {
226
            curl_close($this->curl);
227 644
        }
228 644
    }
229 515
230 644
    /* ------------------------------------------------------------------------------------------------
231
     |  Main Functions
232
     | ------------------------------------------------------------------------------------------------
233
     */
234
    /**
235
     * Get the HTTP.
236
     *
237
     * @return self
238
     */
239
    public static function instance()
240
    {
241 1289
        if ( ! self::$instance) {
242
            self::$instance = new self;
243 1289
        }
244 5
245 4
        return self::$instance;
246
    }
247 1289
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
    public function request($method, $url, $params, $headers, $hasFile)
260
    {
261
        if ($method !== 'post') {
262
            $url    = str_parse_url($url, $params);
263 644
        }
264
        else {
265 644
            $params = $hasFile ? $params : self::encode($params);
266
        }
267 644
268 425
        $this->headers->prepare($this->apiKey, $headers, $hasFile);
269 340
        $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
        $this->options->setOptions([
271 544
            CURLOPT_CONNECTTIMEOUT => $this->connectTimeout,
272
            CURLOPT_TIMEOUT        => $this->timeout,
273
        ]);
274 644
275 644
        $this->init();
276 644
        $this->setOptionArray($this->options->get());
277 644
        $this->execute();
278 644
279 515
        $this->checkCertErrors();
280
        $this->checkResponse();
281 644
282 644
        $statusCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
283 644
        $this->close();
284
285 644
        return [$this->response, $statusCode];
286 644
    }
287
288 644
    /**
289 644
     * Check Cert Errors.
290
     */
291 644
    private function checkCertErrors()
292
    {
293
        if (SslChecker::hasCertErrors($this->errorCode)) {
294
            $this->headers->set(
295
                'X-Stripe-Client-Info',
296
                '{"ca":"using Stripe-supplied CA bundle"}'
297 644
            );
298
299 644
            $this->setOptionArray([
300
                CURLOPT_HTTPHEADER => $this->headers->get(),
301
                CURLOPT_CAINFO     => SslChecker::caBundle()
302
            ]);
303
304
            $this->execute();
305
        }
306
    }
307
308
    /**
309
     * Encode array to query string
310
     *
311
     * @param  array|string  $array
312 644
     * @param  string|null   $prefix
313
     *
314
     * @return string
315
     */
316
    protected static function encode($array, $prefix = null)
317
    {
318
        // @codeCoverageIgnoreStart
319
        if ( ! is_array($array)) return $array;
320
        // @codeCoverageIgnoreEnd
321
322
        $result = [];
323 644
324
        foreach ($array as $key => $value) {
325
            if (is_null($value)) continue;
326
327
            if ($prefix) {
328
                $key = ($key !== null && (! is_int($key) || is_array($value)))
329 644
                    ? $prefix . "[" . $key . "]"
330
                    : $prefix . "[]";
331 644
            }
332 539
333
            if ( ! is_array($value)) {
334 539
                $result[] = urlencode($key) . '=' . urlencode($value);
335 116
            }
336 8
            elseif ($enc = self::encode($value, $key)) {
337 515
                $result[] = $enc;
338
            }
339 644
        }
340
341
        return implode('&', $result);
342
    }
343
344
    /* ------------------------------------------------------------------------------------------------
345
     |  Other Functions
346
     | ------------------------------------------------------------------------------------------------
347
     */
348
    /**
349
     * Check Response.
350
     *
351 10
     * @throws \Arcanedev\Stripe\Exceptions\ApiConnectionException
352
     */
353 10
    private function checkResponse()
354
    {
355 10
        if ($this->response !== false) return;
356
357 10
        $this->close();
358
        $this->handleCurlError();
359
    }
360 10
361 10
    /**
362 10
     * 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 5
                    . '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 5
                $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 5
                    . 'If this problem persists,';
382 5
                break;
383 5
384
            default:
385
                $msg = 'Unexpected error communicating with Stripe. If this problem persists,';
386 5
                // no break
387 5
        }
388 4
389 5
        $msg .= ' let us know at [email protected].';
390 5
391 4
        $msg .= "\n\n(Network error [errno {$this->errorCode}]: {$this->errorMessage})";
392
393 5
        throw new ApiConnectionException($msg);
394 5
    }
395
}
396