Completed
Pull Request — master (#13)
by ARCANEDEV
10:25
created

HttpClient::checkHasResourceFile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
cc 3
eloc 4
nc 3
nop 1
crap 3
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 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 644
    public function setApiKey($apiKey)
116
    {
117 644
        $this->apiKey = $apiKey;
118
119 644
        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 1289
    public function setTimeout($seconds)
154
    {
155 1289
        $this->timeout = (int) max($seconds, 0);
156
157 1289
        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 644
    public function setOptionArray(array $options)
192
    {
193 644
        curl_setopt_array($this->curl, $options);
194
195 644
        return $this;
196
    }
197
198
    /* ------------------------------------------------------------------------------------------------
199
     |  Curl Functions
200
     | ------------------------------------------------------------------------------------------------
201
     */
202
    /**
203
     * Init curl.
204
     */
205 644
    private function init()
206
    {
207 644
        $this->curl = curl_init();
208 644
    }
209
210
    /**
211
     * Execute curl.
212
     */
213 644
    private function execute()
214
    {
215 644
        $this->response     = curl_exec($this->curl);
216 644
        $this->errorCode    = curl_errno($this->curl);
217 644
        $this->errorMessage = curl_error($this->curl);
218 644
    }
219
220
    /**
221
     * Close curl.
222
     */
223 644
    private function close()
224
    {
225 644
        if (is_resource($this->curl)) {
226 644
            curl_close($this->curl);
227 515
        }
228 644
    }
229
230
    /* ------------------------------------------------------------------------------------------------
231
     |  Main Functions
232
     | ------------------------------------------------------------------------------------------------
233
     */
234
    /**
235
     * Get the HTTP.
236
     *
237
     * @return self
238
     */
239 1289
    public static function instance()
240
    {
241 1289
        if ( ! self::$instance) {
242 5
            self::$instance = new self;
243 4
        }
244
245 1289
        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
     * @throws \Arcanedev\Stripe\Exceptions\ApiConnectionException
258
     * @throws \Arcanedev\Stripe\Exceptions\ApiException
259
     *
260
     * @return array
261
     */
262 644
    public function request($method, $url, $params, $headers, $hasFile)
263
    {
264 644
        if ($method !== 'post') {
265 425
            $url    = str_parse_url($url, $params);
266 340
        }
267
        else {
268 544
            $params = $hasFile ? $params : self::encode($params);
0 ignored issues
show
Bug introduced by
It seems like $params defined by $hasFile ? $params : self::encode($params) on line 268 can also be of type string; however, Arcanedev\Stripe\Http\Curl\HttpClient::encode() does only seem to accept array, 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...
269
        }
270
271 644
        $this->headers->prepare($this->apiKey, $headers, $hasFile);
272 644
        $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...
273 644
        $this->options->setOptions([
274 644
            CURLOPT_CONNECTTIMEOUT => $this->connectTimeout,
275 644
            CURLOPT_TIMEOUT        => $this->timeout,
276 515
        ]);
277
278 644
        $this->init();
279 644
        $this->setOptionArray($this->options->get());
280 644
        $this->execute();
281
282 644
        $this->checkCertErrors();
283 644
        $this->checkResponse();
284
285 644
        $statusCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
286 644
        $this->close();
287
288 644
        return [$this->response, $statusCode];
289
    }
290
291
    /**
292
     * Check Cert Errors.
293
     */
294 644
    private function checkCertErrors()
295
    {
296 644
        if (SslChecker::hasCertErrors($this->errorCode)) {
297
            $this->headers->set(
298
                'X-Stripe-Client-Info',
299
                '{"ca":"using Stripe-supplied CA bundle"}'
300
            );
301
302
            $this->setOptionArray([
303
                CURLOPT_HTTPHEADER => $this->headers->get(),
304
                CURLOPT_CAINFO     => SslChecker::caBundle()
305
            ]);
306
307
            $this->execute();
308
        }
309 644
    }
310
311
    /**
312
     * Encode array to query string
313
     *
314
     * @param  array        $array
315
     * @param  string|null  $prefix
316
     *
317
     * @return string
318
     */
319 535
    protected static function encode($array, $prefix = null)
320
    {
321
        // @codeCoverageIgnoreStart
322
        if ( ! is_array($array)) return $array;
323
        // @codeCoverageIgnoreEnd
324
325 535
        $result = [];
326
327 535
        foreach ($array as $key => $value) {
328 525
            if (is_null($value)) {
329 5
                continue;
330
            }
331
332 525
            if ($prefix) {
333 435
                $key = ($key !== null && (! is_int($key) || is_array($value)))
334 435
                    ? $prefix . "[" . $key . "]"
335 435
                    : $prefix . "[]";
336 348
            }
337
338 525
            if ( ! is_array($value)) {
339 525
                $result[] = urlencode($key) . '=' . urlencode($value);
340 420
            }
341 435
            elseif ($enc = self::encode($value, $key)) {
342 453
                $result[] = $enc;
343 348
            }
344 428
        }
345
346 535
        return implode('&', $result);
347
    }
348
349
350
351
    /* ------------------------------------------------------------------------------------------------
352
     |  Other Functions
353
     | ------------------------------------------------------------------------------------------------
354
     */
355
    /**
356
     * Check Response.
357
     *
358
     * @throws \Arcanedev\Stripe\Exceptions\ApiConnectionException
359
     */
360 644
    private function checkResponse()
361
    {
362 644
        if ($this->response !== false) return;
363
364
        $this->close();
365
        $this->handleCurlError();
366
    }
367
368
    /**
369
     * Handle CURL errors.
370
     *
371
     * @throws \Arcanedev\Stripe\Exceptions\ApiConnectionException
372
     */
373
    private function handleCurlError()
374
    {
375
        switch ($this->errorCode) {
376
            case CURLE_COULDNT_CONNECT:
377
            case CURLE_COULDNT_RESOLVE_HOST:
378
            case CURLE_OPERATION_TIMEOUTED:
379
                $msg = 'Could not connect to Stripe (' . $this->apiBaseUrl . '). Please check your internet connection '
380
                    . 'and try again.  If this problem persists, you should check Stripe\'s service status at '
381
                    . 'https://twitter.com/stripestatus, or';
382
                break;
383
384
            case CURLE_SSL_CACERT:
385
            case CURLE_SSL_PEER_CERTIFICATE:
386
                $msg = 'Could not verify Stripe\'s SSL certificate.  Please make sure that your network is not '
387
                    . 'intercepting certificates. (Try going to ' . $this->apiBaseUrl . ' in your browser.) '
388
                    . 'If this problem persists,';
389
                break;
390
391
            default:
392
                $msg = 'Unexpected error communicating with Stripe. If this problem persists,';
393
                // no break
394
        }
395
396
        $msg .= ' let us know at [email protected].';
397
398
        $msg .= "\n\n(Network error [errno {$this->errorCode}]: {$this->errorMessage})";
399
400
        throw new ApiConnectionException($msg);
401
    }
402
}
403