Completed
Push — master ( 6946af...37696c )
by ARCANEDEV
8s
created

Requestor::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 0
cts 0
cp 0
rs 9.4285
cc 1
eloc 4
nc 1
nop 2
crap 2
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 708
    /**
61
     * Create Requestor instance.
62 708
     *
63 708
     * @param  string|null  $apiKey
64 708
     * @param  string|null  $apiBase
65 708
     */
66
    public function __construct($apiKey = null, $apiBase = null)
67
    {
68
        $this->setApiKey($apiKey);
69
        $this->setApiBase($apiBase);
70
        $this->errorsHandler = new ErrorsHandler;
71
    }
72
73
    /* ------------------------------------------------------------------------------------------------
74
     |  Getters & Setters
75
     | ------------------------------------------------------------------------------------------------
76 684
     */
77
    /**
78 684
     * Get Stripe API Key.
79 679
     *
80 543
     * @return string
81
     */
82 684
    public function getApiKey()
83
    {
84
        if ( ! $this->apiKey) {
85
            $this->setApiKey(Stripe::getApiKey());
86
        }
87
88
        return trim($this->apiKey);
89
    }
90
91
    /**
92 708
     * Set API Key
93
     *
94 708
     * @param  string  $apiKey
95
     *
96 708
     * @return self
97
     */
98
    private function setApiKey($apiKey)
99
    {
100
        $this->apiKey = $apiKey;
101
102
        return $this;
103
    }
104
105
    /**
106 708
     * Set API Base URL
107
     *
108 708
     * @param  string|null  $apiBaseUrl
109 29
     *
110 24
     * @return self
111
     */
112 708
    private function setApiBase($apiBaseUrl)
113
    {
114 708
        if (empty($apiBaseUrl)) {
115
            $apiBaseUrl = Stripe::getApiBaseUrl();
116
        }
117
118
        $this->apiBaseUrl = $apiBaseUrl;
119
120
        return $this;
121
    }
122 684
123
    /**
124
     * Get the HTTP client
125
     *
126
     * @return \Arcanedev\Stripe\Contracts\Http\Curl\HttpClientInterface
127
     */
128
    private function httpClient()
129
    {
130 684
        // @codeCoverageIgnoreStart
131
        if ( ! self::$httpClient) {
132
            self::$httpClient = HttpClient::instance();
133
        }
134
        // @codeCoverageIgnoreEnd
135
136
        return self::$httpClient;
137
    }
138 1289
139
    /**
140 1289
     * Set the HTTP client
141 1289
     *
142
     * @param  \Arcanedev\Stripe\Contracts\Http\Curl\HttpClientInterface  $client
143
     */
144
    public static function setHttpClient(HttpClientInterface $client)
145
    {
146
        self::$httpClient = $client;
147
    }
148
149
    /* ------------------------------------------------------------------------------------------------
150
     |  Request Functions
151
     | ------------------------------------------------------------------------------------------------
152
     */
153
    /**
154
     * Make Requestor instance.
155 679
     *
156
     * @param  string|null  $apiKey
157 679
     * @param  string       $apiBase
158
     *
159
     * @return self
160
     */
161
    public static function make($apiKey = null, $apiBase = '')
162
    {
163
        return new self($apiKey, $apiBase);
164
    }
165
166
    /**
167
     * GET Request.
168
     *
169 295
     * @param  string      $url
170
     * @param  array|null  $params
171 295
     * @param  array|null  $headers
172
     *
173
     * @return array
174
     */
175
    public function get($url, $params = [], $headers = null)
176
    {
177
        return $this->request('get', $url, $params, $headers);
178
    }
179
180
    /**
181
     * POST Request.
182
     *
183 35
     * @param  string      $url
184
     * @param  array|null  $params
185 35
     * @param  array|null  $headers
186
     *
187
     * @return array
188
     */
189
    public function post($url, $params = [], $headers = null)
190
    {
191
        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 679
     * @param  array|null  $headers
216
     *
217 679
     * @throws \Arcanedev\Stripe\Exceptions\ApiException
218 679
     *
219
     * @return array
220 679
     */
221 35
    public function request($method, $url, $params = null, $headers = null)
222 28
    {
223
        if (is_null($params))  $params  = [];
224 679
        if (is_null($headers)) $headers = [];
225 325
226 260
        list($respBody, $respCode, $apiKey) = $this->requestRaw($method, $url, $params, $headers);
227
228 679
        $json     = $this->interpretResponse($respBody, $respCode);
229
        $response = new Response($respBody, $respCode, $headers, $json);
230 679
231 649
        return [$response, $apiKey];
232
    }
233 649
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
    private function requestRaw($method, $url, $params, $headers)
249
    {
250 679
        $this->checkApiKey();
251
        $this->checkMethod($method);
252 679
253
        $params = self::encodeObjects($params);
254 679
255
        $this->httpClient()->setApiKey($this->getApiKey());
256 679
257 679
        $hasFile = self::processResourceParams($params);
258
259 679
        list($respBody, $respCode) = $this->httpClient()
260
            ->request($method, $this->apiBaseUrl . $url, $params, $headers, $hasFile);
261
262
        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 684
     *
277
     * @return array
278
     */
279 684
    private function interpretResponse($respBody, $respCode)
280
    {
281 684
        try {
282 141
            $response = json_decode($respBody, true);
283
284
            if (json_last_error() !== JSON_ERROR_NONE) {
285 548
                throw new \Exception;
286 5
            }
287 5
        }
288 5
        catch (\Exception $e) {
289 5
            throw new ApiException(
290 5
                'Invalid response body from API: ' . $respBody . ' (HTTP response code was ' . $respCode .')',
291
                500,
292 4
                'api_error',
293
                null,
294
                $respBody
295 679
            );
296
        }
297 649
298
        $this->errorsHandler->handle($respBody, $respCode, $response);
299
300
        return $response;
301
    }
302
303
    /* ------------------------------------------------------------------------------------------------
304
     |  Check Functions
305
     | ------------------------------------------------------------------------------------------------
306
     */
307
    /**
308
     * Check if API Key Exists.
309 684
     *
310
     * @throws \Arcanedev\Stripe\Exceptions\ApiKeyNotSetException
311 684
     */
312
    private function checkApiKey()
313 684
    {
314
        if ( ! $this->isApiKeyExists()) {
315
            throw new ApiKeyNotSetException('The Stripe API Key is required !');
316
        }
317
    }
318
319
    /**
320
     * Check if the API Key is set.
321 684
     *
322
     * @return bool
323 684
     */
324 5
    private function isApiKeyExists()
325 1
    {
326 4
        $apiKey = $this->getApiKey();
327
328 679
        return ! empty($apiKey);
329
    }
330
331
    /**
332
     * Check Http Method.
333
     *
334
     * @param  string  $method
335
     *
336
     * @throws \Arcanedev\Stripe\Exceptions\ApiException
337 683
     */
338
    private function checkMethod(&$method)
339 683
    {
340
        $method = strtolower($method);
341 683
342 4
        if ( ! in_array($method, self::$allowedMethods)) {
343 4
            throw new ApiException(
344
                "Unrecognized method $method, must be [" . implode(', ', self::$allowedMethods) . '].',
345 4
                500
346
            );
347 679
        }
348
    }
349
350
    /**
351
     * Check Resource type is stream.
352
     *
353
     * @param  resource  $resource
354
     *
355
     * @throws \Arcanedev\Stripe\Exceptions\ApiException
356
     */
357
    private static function checkResourceType($resource)
358
    {
359
        if (get_resource_type($resource) !== 'stream') {
360
            throw new ApiException(
361
                'Attempted to upload a resource that is not a stream'
362 684
            );
363
        }
364 684
    }
365 10
366
    /**
367
     * Check resource MetaData.
368 684
     *
369 60
     * @param  array  $metaData
370
     *
371
     * @throws \Arcanedev\Stripe\Exceptions\ApiException
372 684
     */
373 684
    private static function checkResourceMetaData(array $metaData)
374 564
    {
375 684
        if ($metaData['wrapper_type'] !== 'plainfile') {
376
            throw new ApiException(
377
                'Only plainfile resource streams are supported'
378 559
            );
379
        }
380
    }
381
382
    /**
383
     * Check if param is resource File.
384
     *
385
     * @param  mixed  $resource
386
     *
387
     * @return bool
388
     */
389
    private static function checkHasResourceFile($resource)
390
    {
391
        return
392
            is_resource($resource) ||
393
            (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
    private static function processResourceParams(&$params)
410
    {
411
        // @codeCoverageIgnoreStart
412
        if ( ! is_array($params)) return false;
413
        // @codeCoverageIgnoreEnd
414
415
        $hasFile = false;
416
417
        foreach ($params as $key => $resource) {
418
            $hasFile = self::checkHasResourceFile($resource);
419
420
            if (is_resource($resource)) {
421
                $params[$key] = self::processResourceParam($resource);
422
            }
423
        }
424
425
        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
    private static function processResourceParam($resource)
438
    {
439
        self::checkResourceType($resource);
440
441
        $metaData = stream_get_meta_data($resource);
442
443
        self::checkResourceMetaData($metaData);
444
445
        // We don't have the filename or mimetype, but the API doesn't care
446
        return class_exists('CURLFile')
447
            ? new CURLFile($metaData['uri'])
448
            : '@' . $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
    private static function encodeObjects($obj)
461
    {
462
        if ($obj instanceof StripeResource) {
463
            return str_utf8($obj->id);
464
        }
465
466
        if (is_bool($obj)) {
467
            return $obj ? 'true' : 'false';
468
        }
469
470
        if (is_array($obj)) {
471
            return array_map(function($v) {
472
                return self::encodeObjects($v);
473
            }, $obj);
474
        }
475
476
        return str_utf8($obj);
477
    }
478
}
479