Completed
Push — master ( d181e1...025c95 )
by ARCANEDEV
08:05
created

Requestor::checkResourceMetaData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.5

Importance

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