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

Requestor::get()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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