Passed
Branch master (351ddd)
by Elf
01:32
created

HttpClient::fetchJson()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 3
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace ElfSundae;
4
5
use Closure;
6
use Exception;
7
use ReflectionClass;
8
use GuzzleHttp\Client;
9
use BadMethodCallException;
10
use Illuminate\Support\Arr;
11
use Illuminate\Support\Str;
12
use InvalidArgumentException;
13
use GuzzleHttp\RequestOptions;
14
use Psr\Http\Message\UriInterface;
15
16
/**
17
 * @method $this get(string|UriInterface $uri = '', array $options = [])
18
 * @method $this head(string|UriInterface $uri = '', array $options = [])
19
 * @method $this post(string|UriInterface $uri = '', array $options = [])
20
 * @method $this put(string|UriInterface $uri = '', array $options = [])
21
 * @method $this patch(string|UriInterface $uri = '', array $options = [])
22
 * @method $this delete(string|UriInterface $uri = '', array $options = [])
23
 * @method $this options(string|UriInterface $uri = '', array $options = [])
24
 * @method \GuzzleHttp\Promise\PromiseInterface getAsync(string|UriInterface $uri = '', array $options = [])
25
 * @method \GuzzleHttp\Promise\PromiseInterface headAsync(string|UriInterface $uri = '', array $options = [])
26
 * @method \GuzzleHttp\Promise\PromiseInterface postAsync(string|UriInterface $uri = '', array $options = [])
27
 * @method \GuzzleHttp\Promise\PromiseInterface putAsync(string|UriInterface $uri = '', array $options = [])
28
 * @method \GuzzleHttp\Promise\PromiseInterface patchAsync(string|UriInterface $uri = '', array $options = [])
29
 * @method \GuzzleHttp\Promise\PromiseInterface deleteAsync(string|UriInterface $uri = '', array $options = [])
30
 * @method \GuzzleHttp\Promise\PromiseInterface optionsAsync(string|UriInterface $uri = '', array $options = [])
31
 * @method int getStatusCode()
32
 * @method string getReasonPhrase()
33
 * @method string getProtocolVersion()
34
 * @method array getHeaders()
35
 * @method bool hasHeader(string $header)
36
 * @method array getHeader(string $header)
37
 * @method string getHeaderLine(string $header)
38
 * @method \Psr\Http\Message\StreamInterface getBody()
39
 * @method $this allowRedirects(bool|array $value)
40
 * @method $this auth(array|string|null $value)
41
 * @method $this body(mixed $value)
42
 * @method $this cert(string|array $value)
43
 * @method $this cookies(bool|\GuzzleHttp\Cookie\CookieJarInterface $value)
44
 * @method $this connectTimeout(float $value)
45
 * @method $this debug(bool|resource $value)
46
 * @method $this decodeContent(bool $value)
47
 * @method $this delay(int|float $value)
48
 * @method $this expect(bool|int $value)
49
 * @method $this formParams(array $value)
50
 * @method $this headers(array $value)
51
 * @method $this httpErrors(bool $value)
52
 * @method $this json(mixed $value)
53
 * @method $this multipart(array $value)
54
 * @method $this onHeaders(callable $value)
55
 * @method $this onStats(callable $value)
56
 * @method $this progress(callable $value)
57
 * @method $this proxy(string|array $value)
58
 * @method $this query(array|string $value)
59
 * @method $this sink(string|resource|\Psr\Http\Message\StreamInterface $value)
60
 * @method $this sslKey(array|string $value)
61
 * @method $this stream(bool $value)
62
 * @method $this verify(bool|string $value)
63
 * @method $this timeout(float $value)
64
 * @method $this readTimeout(float $value)
65
 * @method $this version(float|string $value)
66
 * @method $this forceIpResolve(string $value)
67
 *
68
 * @see http://docs.guzzlephp.org/en/stable/request-options.html Request Options
69
 */
70
class HttpClient
71
{
72
    /**
73
     * The default request options.
74
     *
75
     * @var array
76
     */
77
    protected static $defaultOptions = [
78
        'connect_timeout' => 5,
79
        'timeout' => 20,
80
        'http_errors' => false,
81
    ];
82
83
    /**
84
     * The Guzzle client.
85
     *
86
     * @var \GuzzleHttp\Client
87
     */
88
    protected $client;
89
90
    /**
91
     * The request options.
92
     *
93
     * @var array
94
     */
95
    protected $options = [];
96
97
    /**
98
     * The Guzzle response.
99
     *
100
     * @var \GuzzleHttp\Psr7\Response
101
     */
102
    protected $response;
103
104
    /**
105
     * Indicate whether to catch Guzzle exceptions.
106
     *
107
     * @var bool
108
     */
109 109
    protected $catchExceptions = true;
110
111 109
    /**
112
     * Get the default request options.
113
     *
114
     * @return array
115
     */
116
    public static function defaultOptions()
117
    {
118
        return static::$defaultOptions;
119
    }
120 113
121
    /**
122 113
     * Set the default request options.
123 113
     *
124
     * @param  array  $options
125
     * @return void
126
     */
127
    public static function setDefaultOptions(array $options)
128
    {
129
        static::$defaultOptions = $options;
130
    }
131
132 109
    /**
133
     * Create a new HTTP client instance.
134 109
     *
135 4
     * @param  array|string|\Psr\Http\Message\UriInterface  $options  base URI or any request options
136 106
     * @return static
137 4
     */
138
    public static function create($options = [])
139
    {
140 105
        return new static($options);
141 105
    }
142 27
143
    /**
144 105
     * Create a new HTTP client instance.
145 105
     *
146
     * @param  array|string|\Psr\Http\Message\UriInterface  $options  base URI or any request options
147
     *
148
     * @throws \InvalidArgumentException
149
     */
150
    public function __construct($options = [])
151
    {
152 16
        if (is_string($options) || $options instanceof UriInterface) {
153
            $options = ['base_uri' => $options];
154 16
        } elseif (! is_array($options)) {
155
            throw new InvalidArgumentException('Options must be a string, UriInterface, or an array');
156
        }
157
158
        $this->client = new Client(
159
            array_replace_recursive(static::defaultOptions(), $options)
160
        );
161
162 4
        $this->options = $this->client->getConfig();
163
    }
164 4
165
    /**
166
     * Get the Guzzle client instance.
167
     *
168
     * @return \GuzzleHttp\Client
169
     */
170
    public function getClient()
171
    {
172
        return $this->client;
173 8
    }
174
175 8
    /**
176
     * Get whether to catch Guzzle exceptions or not.
177 8
     *
178
     * @return bool
179
     */
180
    public function areExceptionsCaught()
181
    {
182
        return $this->catchExceptions;
183
    }
184
185
    /**
186 48
     * Set whether to catch Guzzle exceptions or not.
187
     *
188 48
     * @param  bool  $catch
189
     * @return $this
190
     */
191
    public function catchExceptions($catch)
192
    {
193
        $this->catchExceptions = (bool) $catch;
194
195
        return $this;
196
    }
197
198 36
    /**
199
     * Get the request options using "dot" notation.
200 36
     *
201
     * @param  string|null  $key
202 36
     * @return mixed
203 36
     */
204 9
    public function getOption($key = null)
205
    {
206 36
        return Arr::get($this->options, $key);
207
    }
208
209
    /**
210
     * Set the request options using "dot" notation.
211
     *
212
     * @param  string|array  $key
213
     * @param  mixed  $value
214
     * @return $this
215 4
     */
216
    public function option($key, $value = null)
217 4
    {
218
        $keys = is_array($key) ? $key : [$key => $value];
219 4
220
        foreach ($keys as $key => $value) {
221
            Arr::set($this->options, $key, $value);
222
        }
223
224
        return $this;
225
    }
226
227
    /**
228 8
     * Merge the given options to the request options.
229
     *
230 8
     * @param  array  $options
231
     * @return $this
232 8
     */
233
    public function mergeOptions(array $options)
234
    {
235
        $this->options = array_replace_recursive($this->options, $options);
236
237
        return $this;
238
    }
239
240
    /**
241
     * Remove one or many request options using "dot" notation.
242 24
     *
243
     * @param  array|string  $keys
244 24
     * @return $this
245
     */
246
    public function removeOption($keys)
247
    {
248
        Arr::forget($this->options, is_array($keys) ? $keys : func_get_args());
249
250
        return $this;
251
    }
252
253 12
    /**
254
     * Set a request header.
255 12
     *
256
     * @param  string  $name
257
     * @param  mixed  $value
258
     * @return $this
259
     */
260
    public function header($name, $value)
261
    {
262
        return $this->option('headers.'.$name, $value);
263 4
    }
264
265 4
    /**
266
     * Set the request accept type.
267
     *
268
     * @param  string  $type
269
     * @return $this
270
     */
271
    public function accept($type)
272
    {
273
        return $this->header('Accept', $type);
274 4
    }
275
276 4
    /**
277
     * Set the request accept type to "application/json".
278
     *
279
     * @return $this
280
     */
281
    public function acceptJson()
282
    {
283
        return $this->accept('application/json');
284
    }
285 4
286
    /**
287 4
     * Set user agent for the request.
288
     *
289
     * @param  string  $value
290
     * @return $this
291
     */
292
    public function userAgent($value)
293
    {
294
        return $this->header('User-Agent', $value);
295
    }
296
297 4
    /**
298
     * Set the request content type.
299 4
     *
300
     * @param  string  $type
301
     * @return $this
302
     */
303
    public function contentType($type)
304
    {
305
        return $this->header('Content-Type', $type);
306
    }
307 12
308
    /**
309 12
     * Specify where the body of the response will be saved.
310
     * Set the "sink" option.
311
     *
312
     * @param  string|resource|\Psr\Http\Message\StreamInterface  $dest
313
     * @return $this
314
     */
315
    public function saveTo($dest)
316
    {
317
        return $this->removeOption('save_to')->option('sink', $dest);
318
    }
319
320 24
    /**
321
     * Get the Guzzle response instance.
322 24
     *
323 18
     * @return \GuzzleHttp\Psr7\Response|null
324 9
     */
325 24
    public function getResponse()
326
    {
327
        return $this->response;
328 4
    }
329
330
    /**
331
     * Get data from the response.
332
     *
333
     * @param  string|\Closure  $callback
334
     * @param  mixed  $parameters
335
     * @param  mixed  $default
336 16
     * @return mixed
337
     */
338 16
    public function getResponseData($callback, $parameters = [], $default = null)
339
    {
340
        if ($this->response) {
341
            return $callback instanceof Closure
342
                ? $callback($this->response, ...(array) $parameters)
343
                : $this->response->$callback(...(array) $parameters);
344
        }
345
346
        return $default;
347 8
    }
348
349 8
    /**
350
     * Get the response content.
351
     *
352
     * @return string
353
     */
354
    public function getContent()
355
    {
356
        return (string) $this->getBody();
357
    }
358
359
    /**
360 41
     * Get the JSON-decoded response content.
361
     *
362 41
     * @param  bool  $assoc
363
     * @return mixed
364 41
     */
365 41
    public function getJson($assoc = true)
366
    {
367
        return json_decode($this->getContent(), $assoc);
368 41
    }
369 14
370 4
    /**
371 4
     * Make request to a URI.
372
     *
373
     * @param  string|\Psr\Http\Message\UriInterface  $uri
374
     * @param  string  $method
375 41
     * @param  array  $options
376
     * @return $this
377
     */
378
    public function request($uri = '', $method = 'GET', array $options = [])
379
    {
380
        $this->response = null;
381
382
        $method = strtoupper($method);
383
        $options = array_replace_recursive($this->options, $options);
384
385
        try {
386 8
            $this->response = $this->client->request($method, $uri, $options);
387
        } catch (Exception $e) {
388 8
            if (! $this->catchExceptions) {
389 8
                throw $e;
390 2
            }
391
        }
392 8
393
        return $this;
394
    }
395
396
    /**
397
     * Make request to a URI, expecting JSON content.
398
     *
399
     * @param  string|\Psr\Http\Message\UriInterface  $uri
400
     * @param  string  $method
401 8
     * @param  array  $options
402
     * @return $this
403 8
     */
404
    public function requestJson($uri = '', $method = 'GET', array $options = [])
405 8
    {
406 8
        $options = $this->addAcceptableJsonType(
407 8
            array_replace_recursive($this->options, $options)
408 2
        );
409
410 8
        return $this->request($uri, $method, $options);
411
    }
412
413
    /**
414
     * Add JSON type to the "Accept" header for the request options.
415
     *
416
     * @param  array  $options
417
     * @return array
418
     */
419
    protected function addAcceptableJsonType(array $options)
420
    {
421 4
        $accept = Arr::get($options, 'headers.Accept', '');
422
423 4
        if (! Str::contains($accept, ['/json', '+json'])) {
424
            $accept = rtrim('application/json,'.$accept, ',');
425
            Arr::set($options, 'headers.Accept', $accept);
426
        }
427
428
        return $options;
429
    }
430
431
    /**
432
     * Make asynchronous request to a URI.
433
     *
434 4
     * @param  string|\Psr\Http\Message\UriInterface  $uri
435
     * @param  string  $method
436 4
     * @param  array  $options
437
     * @return \GuzzleHttp\Promise\PromiseInterface
438
     */
439
    public function requestAsync($uri = '', $method = 'GET', array $options = [])
440
    {
441
        return $this->client->requestAsync(
442
            strtoupper($method),
443
            $uri,
444 33
            array_replace_recursive($this->options, $options)
445
        );
446
    }
447 33
448 9
    /**
449
     * Request the URI and return the response content.
450
     *
451
     * @param  string|\Psr\Http\Message\UriInterface  $uri
452
     * @param  string  $method
453
     * @param  array  $options
454
     * @return string
455
     */
456
    public function fetchContent($uri = '', $method = 'GET', array $options = [])
457
    {
458 5
        return $this->request($uri, $method, $options)->getContent();
459
    }
460 5
461 1
    /**
462 1
     * Request the URI and return the JSON-decoded response content.
463 5
     *
464
     * @param  string|\Psr\Http\Message\UriInterface  $uri
465
     * @param  string  $method
466 5
     * @param  array  $options
467
     * @return mixed
468
     */
469
    public function fetchJson($uri = '', $method = 'GET', array $options = [])
470
    {
471
        return $this->requestJson($uri, $method, $options)->getJson();
472
    }
473
474 28
    /**
475
     * Get all allowed magic request methods.
476
     *
477 28
     * @return array
478 7
     */
479 7
    protected function getMagicRequestMethods()
480
    {
481
        return [
482
            'get', 'head', 'post', 'put', 'patch', 'delete', 'options',
483
        ];
484
    }
485
486
    /**
487 8
     * Determine if the given method is a magic request method.
488
     *
489 8
     * @param  string  $method
490
     * @param  string  &$requestMethod
491 8
     * @param  string  &$httpMethod
492 4
     * @return bool
493 4
     */
494 4
    protected function isMagicRequest($method, &$requestMethod, &$httpMethod)
495 4
    {
496 1
        if (strlen($method) > 5 && $pos = strrpos($method, 'Async', -5)) {
497 1
            $httpMethod = substr($method, 0, $pos);
498
            $requestMethod = 'requestAsync';
499 8
        } else {
500
            $httpMethod = $method;
501
            $requestMethod = 'request';
502
        }
503
504
        if (in_array($httpMethod, $this->getMagicRequestMethods())) {
505
            return true;
506
        }
507
508 8
        $httpMethod = $requestMethod = null;
509
510 8
        return false;
511 4
    }
512
513 4
    /**
514
     * Get parameters for $this->request() from the magic request call.
515
     *
516
     * @param  string  $method
517
     * @param  array  $parameters
518
     * @return array
519
     */
520
    protected function getRequestParameters($method, array $parameters)
521
    {
522
        if (empty($parameters)) {
523
            $parameters = ['', $method];
524
        } else {
525
            array_splice($parameters, 1, 0, $method);
526 33
        }
527
528 33
        return $parameters;
529 5
    }
530
531
    /**
532 28
     * Get all allowed magic response methods.
533 20
     *
534
     * @return array
535
     */
536 8
    protected function getMagicResponseMethods()
537 4
    {
538 4
        return [
539
            'getStatusCode', 'getReasonPhrase', 'getProtocolVersion',
540
            'getHeaders', 'hasHeader', 'getHeader', 'getHeaderLine', 'getBody',
541 4
        ];
542
    }
543
544 4
    /**
545
     * Get all allowed magic option methods.
546
     *
547
     * @return array
548
     */
549
    protected function getMagicOptionMethods()
550
    {
551
        static $optionMethods = null;
552
553
        if (is_null($optionMethods)) {
554
            $reflector = new ReflectionClass(RequestOptions::class);
555
            $optionMethods = array_map(
556
                [Str::class, 'camel'],
557
                array_values($reflector->getConstants())
558
            );
559
        }
560
561
        return $optionMethods;
562
    }
563
564
    /**
565
     * Get the option name for the given magic option method.
566
     *
567
     * @param  string  $method
568
     * @return string|null
569
     */
570
    protected function getOptionNameForMethod($method)
571
    {
572
        if (in_array($method, $this->getMagicOptionMethods())) {
573
            return Str::snake($method);
574
        }
575
    }
576
577
    /**
578
     * Handle magic method to send request, get response data, or set
579
     * request options.
580
     *
581
     * @param  string  $method
582
     * @param  array  $parameters
583
     * @return mixed
584
     *
585
     * @throws \InvalidArgumentException
586
     * @throws \BadMethodCallException
587
     */
588
    public function __call($method, $parameters)
589
    {
590
        if ($this->isMagicRequest($method, $requestMethod, $httpMethod)) {
591
            return $this->$requestMethod(...$this->getRequestParameters($httpMethod, $parameters));
592
        }
593
594
        if (in_array($method, $this->getMagicResponseMethods())) {
595
            return $this->getResponseData($method, $parameters);
596
        }
597
598
        if ($option = $this->getOptionNameForMethod($method)) {
599
            if (empty($parameters)) {
600
                throw new InvalidArgumentException("Method [$method] needs one argument.");
601
            }
602
603
            return $this->option($option, $parameters[0]);
604
        }
605
606
        throw new BadMethodCallException("Method [$method] does not exist.");
607
    }
608
}
609