Completed
Push — master ( 388c50...c3616e )
by ARCANEDEV
8s
created

Requestor::request()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

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