Completed
Pull Request — master (#15)
by ARCANEDEV
11:13
created

Requestor::interpretResponse()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 23
ccs 13
cts 13
cp 1
rs 9.0856
cc 3
eloc 14
nc 4
nop 2
crap 3
1
<?php namespace Arcanedev\Stripe\Http;
2
3
use Arcanedev\Stripe\Contracts\Http\Curl\HttpClientInterface;
4
use Arcanedev\Stripe\Contracts\Http\RequestorInterface;
5
use Arcanedev\Stripe\Exceptions\ApiException;
6
use Arcanedev\Stripe\Exceptions\ApiKeyNotSetException;
7
use Arcanedev\Stripe\Http\Curl\HttpClient;
8
use Arcanedev\Stripe\Stripe;
9
use Arcanedev\Stripe\StripeResource;
10
use Arcanedev\Stripe\Utilities\ErrorsHandler;
11
use CURLFile;
12
13
/**
14
 * Class     Requestor
15
 *
16
 * @package  Arcanedev\Stripe\Http
17
 * @author   ARCANEDEV <[email protected]>
18
 */
19
class Requestor implements RequestorInterface
20
{
21
    /* ------------------------------------------------------------------------------------------------
22
     |  Properties
23
     | ------------------------------------------------------------------------------------------------
24
     */
25
    /**
26
     * The API key that's to be used to make requests.
27
     *
28
     * @var string
29
     */
30
    private $apiKey;
31
32
    /**
33
     * The API base URL.
34
     *
35
     * @var string
36
     */
37
    private $apiBaseUrl;
38
39
    /**
40
     * @var \Arcanedev\Stripe\Contracts\Http\Curl\HttpClientInterface
41
     */
42
    private static $httpClient;
43
44
    /**
45
     * The allowed HTTP methods.
46
     *
47
     * @var array
48
     */
49
    private static $allowedMethods = ['get', 'post', 'delete'];
50
51
    /**
52
     * @var \Arcanedev\Stripe\Utilities\ErrorsHandler
53
     */
54
    private $errorsHandler;
55
56
    /* ------------------------------------------------------------------------------------------------
57
     |  Getters & Setters
58
     | ------------------------------------------------------------------------------------------------
59
     */
60
    /**
61
     * Create Requestor instance.
62
     *
63
     * @param  string|null  $apiKey
64
     * @param  string|null  $apiBase
65
     */
66 713
    public function __construct($apiKey = null, $apiBase = null)
67
    {
68 713
        $this->setApiKey($apiKey);
69 713
        $this->setApiBase($apiBase);
70 713
        $this->errorsHandler = new ErrorsHandler;
71 713
    }
72
73
    /* ------------------------------------------------------------------------------------------------
74
     |  Getters & Setters
75
     | ------------------------------------------------------------------------------------------------
76
     */
77
    /**
78
     * Get Stripe API Key.
79
     *
80
     * @return string
81
     */
82 689
    public function getApiKey()
83
    {
84 689
        if ( ! $this->apiKey) {
85 684
            $this->setApiKey(Stripe::getApiKey());
86 547
        }
87
88 689
        return trim($this->apiKey);
89
    }
90
91
    /**
92
     * Set API Key
93
     *
94
     * @param  string  $apiKey
95
     *
96
     * @return self
97
     */
98 713
    private function setApiKey($apiKey)
99
    {
100 713
        $this->apiKey = $apiKey;
101
102 713
        return $this;
103
    }
104
105
    /**
106
     * Set API Base URL
107
     *
108
     * @param  string|null  $apiBaseUrl
109
     *
110
     * @return self
111
     */
112 713
    private function setApiBase($apiBaseUrl)
113
    {
114 713
        if (empty($apiBaseUrl)) {
115 29
            $apiBaseUrl = Stripe::getApiBaseUrl();
116 24
        }
117
118 713
        $this->apiBaseUrl = $apiBaseUrl;
119
120 713
        return $this;
121
    }
122
123
    /**
124
     * Get the HTTP client
125
     *
126
     * @return \Arcanedev\Stripe\Contracts\Http\Curl\HttpClientInterface
127
     */
128 689
    private function httpClient()
129
    {
130
        // @codeCoverageIgnoreStart
131
        if ( ! self::$httpClient) {
132
            self::$httpClient = HttpClient::instance();
133
        }
134
        // @codeCoverageIgnoreEnd
135
136 689
        return self::$httpClient;
137
    }
138
139
    /**
140
     * Set the HTTP client
141
     *
142
     * @param  \Arcanedev\Stripe\Contracts\Http\Curl\HttpClientInterface  $client
143
     */
144 1294
    public static function setHttpClient(HttpClientInterface $client)
145
    {
146 1294
        self::$httpClient = $client;
147 1294
    }
148
149
    /* ------------------------------------------------------------------------------------------------
150
     |  Request Functions
151
     | ------------------------------------------------------------------------------------------------
152
     */
153
    /**
154
     * Make Requestor instance.
155
     *
156
     * @param  string|null  $apiKey
157
     * @param  string       $apiBase
158
     *
159
     * @return self
160
     */
161 684
    public static function make($apiKey = null, $apiBase = '')
162
    {
163 684
        return new self($apiKey, $apiBase);
164
    }
165
166
    /**
167
     * GET Request.
168
     *
169
     * @param  string      $url
170
     * @param  array|null  $params
171
     * @param  array|null  $headers
172
     *
173
     * @return array
174
     */
175 306
    public function get($url, $params = [], $headers = null)
176
    {
177 306
        return $this->request('get', $url, $params, $headers);
178
    }
179
180
    /**
181
     * POST Request.
182
     *
183
     * @param  string      $url
184
     * @param  array|null  $params
185
     * @param  array|null  $headers
186
     *
187
     * @return array
188
     */
189 45
    public function post($url, $params = [], $headers = null)
190
    {
191 45
        return $this->request('post', $url, $params, $headers);
192
    }
193
194
    /**
195
     * DELETE Request.
196
     *
197
     * @param  string      $url
198
     * @param  array|null  $params
199
     * @param  array|null  $headers
200
     *
201
     * @return array
202
     */
203
    public function delete($url, $params = [], $headers = null)
204
    {
205
        return $this->request('delete', $url, $params, $headers);
206
    }
207
208
    /**
209
     * Make a request.
210
     * Note: An array whose first element is the Response object and second element is the API key used to make the request.
211
     *
212
     * @param  string      $method
213
     * @param  string      $url
214
     * @param  array|null  $params
215
     * @param  array|null  $headers
216
     *
217
     * @throws \Arcanedev\Stripe\Exceptions\ApiException
218
     *
219
     * @return array
220
     */
221 684
    public function request($method, $url, $params = null, $headers = null)
222
    {
223 684
        if (is_null($params))  $params  = [];
224 684
        if (is_null($headers)) $headers = [];
225
226 684
        list($respBody, $respCode, $apiKey) = $this->requestRaw($method, $url, $params, $headers);
227
228 684
        $json     = $this->interpretResponse($respBody, $respCode);
229 654
        $response = new Response($respBody, $respCode, $headers, $json);
230
231 654
        return [$response, $apiKey];
232
    }
233
234
    /**
235
     * Raw request.
236
     *
237
     * @param  string  $method
238
     * @param  string  $url
239
     * @param  array   $params
240
     * @param  array   $headers
241
     *
242
     * @throws \Arcanedev\Stripe\Exceptions\ApiConnectionException
243
     * @throws \Arcanedev\Stripe\Exceptions\ApiException
244
     * @throws \Arcanedev\Stripe\Exceptions\ApiKeyNotSetException
245
     *
246
     * @return array
247
     */
248 684
    private function requestRaw($method, $url, $params, $headers)
249
    {
250 684
        $this->checkApiKey();
251 684
        $this->checkMethod($method);
252
253 684
        $params = self::encodeObjects($params);
254
255 684
        $this->httpClient()->setApiKey($this->getApiKey());
256
257 684
        $hasFile = self::processResourceParams($params);
258
259 684
        list($respBody, $respCode) = $this->httpClient()
260 684
            ->request($method, $this->apiBaseUrl . $url, $params, $headers, $hasFile);
261
262 684
        return [$respBody, $respCode, $this->getApiKey()];
263
    }
264
265
    /**
266
     * Interpret Response.
267
     *
268
     * @param  string  $respBody
269
     * @param  int     $respCode
270
     *
271
     * @throws \Arcanedev\Stripe\Exceptions\ApiException
272
     * @throws \Arcanedev\Stripe\Exceptions\AuthenticationException
273
     * @throws \Arcanedev\Stripe\Exceptions\CardException
274
     * @throws \Arcanedev\Stripe\Exceptions\InvalidRequestException
275
     * @throws \Arcanedev\Stripe\Exceptions\RateLimitException
276
     *
277
     * @return array
278
     */
279 689
    private function interpretResponse($respBody, $respCode)
280
    {
281
        try {
282 689
            $response = json_decode($respBody, true);
283
284 689
            if (json_last_error() !== JSON_ERROR_NONE) {
285 142
                throw new \Exception;
286
            }
287
        }
288 552
        catch (\Exception $e) {
289 5
            throw new ApiException(
290 5
                'Invalid response body from API: ' . $respBody . ' (HTTP response code was ' . $respCode .')',
291 5
                500,
292 5
                'api_error',
293 5
                null,
294
                $respBody
295 4
            );
296
        }
297
298 684
        $this->errorsHandler->handle($respBody, $respCode, $response);
299
300 654
        return $response;
301
    }
302
303
    /* ------------------------------------------------------------------------------------------------
304
     |  Check Functions
305
     | ------------------------------------------------------------------------------------------------
306
     */
307
    /**
308
     * Check if API Key Exists.
309
     *
310
     * @throws \Arcanedev\Stripe\Exceptions\ApiKeyNotSetException
311
     */
312 689
    private function checkApiKey()
313
    {
314 689
        if ( ! $this->isApiKeyExists()) {
315 5
            throw new ApiKeyNotSetException('The Stripe API Key is required !');
316
        }
317 684
    }
318
319
    /**
320
     * Check if the API Key is set.
321
     *
322
     * @return bool
323
     */
324 689
    private function isApiKeyExists()
325
    {
326 689
        $apiKey = $this->getApiKey();
327
328 689
        return ! empty($apiKey);
329
    }
330
331
    /**
332
     * Check Http Method.
333
     *
334
     * @param  string  $method
335
     *
336
     * @throws \Arcanedev\Stripe\Exceptions\ApiException
337
     */
338 688
    private function checkMethod(&$method)
339
    {
340 688
        $method = strtolower($method);
341
342 688
        if ( ! in_array($method, self::$allowedMethods)) {
343 4
            throw new ApiException(
344 4
                "Unrecognized method $method, must be [" . implode(', ', self::$allowedMethods) . '].',
345
                500
346 4
            );
347
        }
348 684
    }
349
350
    /**
351
     * Check Resource type is stream.
352
     *
353
     * @param  resource  $resource
354
     *
355
     * @throws \Arcanedev\Stripe\Exceptions\ApiException
356
     */
357 10
    private static function checkResourceType($resource)
358
    {
359 10
        if (get_resource_type($resource) !== 'stream') {
360
            throw new ApiException(
361
                'Attempted to upload a resource that is not a stream'
362
            );
363
        }
364 10
    }
365
366
    /**
367
     * Check resource MetaData.
368
     *
369
     * @param  array  $metaData
370
     *
371
     * @throws \Arcanedev\Stripe\Exceptions\ApiException
372
     */
373 10
    private static function checkResourceMetaData(array $metaData)
374
    {
375 10
        if ($metaData['wrapper_type'] !== 'plainfile') {
376
            throw new ApiException(
377
                'Only plainfile resource streams are supported'
378
            );
379
        }
380 10
    }
381
382
    /**
383
     * Check if param is resource File.
384
     *
385
     * @param  mixed  $resource
386
     *
387
     * @return bool
388
     */
389 564
    private static function checkHasResourceFile($resource)
390
    {
391
        return
392 564
            is_resource($resource) ||
393 564
            (class_exists('CURLFile') && $resource instanceof CURLFile);
0 ignored issues
show
Bug introduced by
The class CURLFile does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
394
    }
395
396
    /* ------------------------------------------------------------------------------------------------
397
     |  Other Functions
398
     | ------------------------------------------------------------------------------------------------
399
     */
400
    /**
401
     * Process Resource Parameters.
402
     *
403
     * @param  array|string  $params
404
     *
405
     * @throws \Arcanedev\Stripe\Exceptions\ApiException
406
     *
407
     * @return bool
408
     */
409 684
    private static function processResourceParams(&$params)
410
    {
411
        // @codeCoverageIgnoreStart
412
        if ( ! is_array($params)) return false;
413
        // @codeCoverageIgnoreEnd
414
415 684
        $hasFile = false;
416
417 684
        foreach ($params as $key => $resource) {
418 564
            $hasFile = self::checkHasResourceFile($resource);
419
420 564
            if (is_resource($resource)) {
421 121
                $params[$key] = self::processResourceParam($resource);
422 8
            }
423 547
        }
424
425 684
        return $hasFile;
426
    }
427
428
    /**
429
     * Process Resource Parameter.
430
     *
431
     * @param  resource  $resource
432
     *
433
     * @throws \Arcanedev\Stripe\Exceptions\ApiException
434
     *
435
     * @return \CURLFile|string
436
     */
437 10
    private static function processResourceParam($resource)
438
    {
439 10
        self::checkResourceType($resource);
440
441 10
        $metaData = stream_get_meta_data($resource);
442
443 10
        self::checkResourceMetaData($metaData);
444
445
        // We don't have the filename or mimetype, but the API doesn't care
446 10
        return class_exists('CURLFile')
447 10
            ? new CURLFile($metaData['uri'])
448 10
            : '@' . $metaData['uri'];
449
    }
450
451
    /**
452
     * Encode Objects.
453
     *
454
     * @param  \Arcanedev\Stripe\StripeResource|bool|array|string  $obj
455
     *
456
     * @throws \Arcanedev\Stripe\Exceptions\ApiException
457
     *
458
     * @return array|string
459
     */
460 689
    private static function encodeObjects($obj)
461
    {
462 689
        if ($obj instanceof StripeResource) {
463 10
            return str_utf8($obj->id);
464
        }
465
466 689
        if (is_bool($obj)) {
467 65
            return $obj ? 'true' : 'false';
468
        }
469
470 689
        if (is_array($obj)) {
471 689
            return array_map(function($v) {
472 569
                return self::encodeObjects($v);
473 689
            }, $obj);
474
        }
475
476 564
        return str_utf8($obj);
477
    }
478
}
479