Passed
Push — master ( 13492b...377462 )
by Elf
02:54
created

HttpClient::areExceptionsCaught()   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 0
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
     *
137 4
     * @throws \InvalidArgumentException
138
     */
139
    public function __construct($options = [])
140 105
    {
141 105
        if (is_string($options) || $options instanceof UriInterface) {
142 27
            $options = ['base_uri' => $options];
143
        } elseif (! is_array($options)) {
144 105
            throw new InvalidArgumentException('Options must be a string, UriInterface, or an array');
145 105
        }
146
147
        $this->client = new Client(
148
            array_replace_recursive(static::defaultOptions(), $options)
149
        );
150
151
        $this->options = $this->client->getConfig();
152 16
    }
153
154 16
    /**
155
     * Get the Guzzle client instance.
156
     *
157
     * @return \GuzzleHttp\Client
158
     */
159
    public function getClient()
160
    {
161
        return $this->client;
162 4
    }
163
164 4
    /**
165
     * Get whether to catch Guzzle exceptions or not.
166
     *
167
     * @return bool
168
     */
169
    public function areExceptionsCaught()
170
    {
171
        return $this->catchExceptions;
172
    }
173 8
174
    /**
175 8
     * Set whether to catch Guzzle exceptions or not.
176
     *
177 8
     * @param  bool  $catch
178
     * @return $this
179
     */
180
    public function catchExceptions($catch)
181
    {
182
        $this->catchExceptions = (bool) $catch;
183
184
        return $this;
185
    }
186 48
187
    /**
188 48
     * Get the request options using "dot" notation.
189
     *
190
     * @param  string|null  $key
191
     * @return mixed
192
     */
193
    public function getOption($key = null)
194
    {
195
        return Arr::get($this->options, $key);
196
    }
197
198 36
    /**
199
     * Set the request options using "dot" notation.
200 36
     *
201
     * @param  string|array  $key
202 36
     * @param  mixed  $value
203 36
     * @return $this
204 9
     */
205
    public function option($key, $value = null)
206 36
    {
207
        $keys = is_array($key) ? $key : [$key => $value];
208
209
        foreach ($keys as $key => $value) {
210
            Arr::set($this->options, $key, $value);
211
        }
212
213
        return $this;
214
    }
215 4
216
    /**
217 4
     * Merge the given options to the request options.
218
     *
219 4
     * @param  array  $options
220
     * @return $this
221
     */
222
    public function mergeOptions(array $options)
223
    {
224
        $this->options = array_replace_recursive($this->options, $options);
225
226
        return $this;
227
    }
228 8
229
    /**
230 8
     * Remove one or many request options using "dot" notation.
231
     *
232 8
     * @param  array|string  $keys
233
     * @return $this
234
     */
235
    public function removeOption($keys)
236
    {
237
        Arr::forget($this->options, is_array($keys) ? $keys : func_get_args());
238
239
        return $this;
240
    }
241
242 24
    /**
243
     * Set a request header.
244 24
     *
245
     * @param  string  $name
246
     * @param  mixed  $value
247
     * @return $this
248
     */
249
    public function header($name, $value)
250
    {
251
        return $this->option('headers.'.$name, $value);
252
    }
253 12
254
    /**
255 12
     * Set the request accept type.
256
     *
257
     * @param  string  $type
258
     * @return $this
259
     */
260
    public function accept($type)
261
    {
262
        return $this->header('Accept', $type);
263 4
    }
264
265 4
    /**
266
     * Set the request accept type to "application/json".
267
     *
268
     * @return $this
269
     */
270
    public function acceptJson()
271
    {
272
        return $this->accept('application/json');
273
    }
274 4
275
    /**
276 4
     * Set user agent for the request.
277
     *
278
     * @param  string  $value
279
     * @return $this
280
     */
281
    public function userAgent($value)
282
    {
283
        return $this->header('User-Agent', $value);
284
    }
285 4
286
    /**
287 4
     * Set the request content type.
288
     *
289
     * @param  string  $type
290
     * @return $this
291
     */
292
    public function contentType($type)
293
    {
294
        return $this->header('Content-Type', $type);
295
    }
296
297 4
    /**
298
     * Specify where the body of the response will be saved.
299 4
     * Set the "sink" option.
300
     *
301
     * @param  string|resource|\Psr\Http\Message\StreamInterface  $dest
302
     * @return $this
303
     */
304
    public function saveTo($dest)
305
    {
306
        return $this->removeOption('save_to')->option('sink', $dest);
307 12
    }
308
309 12
    /**
310
     * Get the Guzzle response instance.
311
     *
312
     * @return \GuzzleHttp\Psr7\Response|null
313
     */
314
    public function getResponse()
315
    {
316
        return $this->response;
317
    }
318
319
    /**
320 24
     * Get data from the response.
321
     *
322 24
     * @param  string|\Closure  $callback
323 18
     * @param  mixed  $parameters
324 9
     * @param  mixed  $default
325 24
     * @return mixed
326
     */
327
    public function getResponseData($callback, $parameters = [], $default = null)
328 4
    {
329
        if ($this->response) {
330
            return $callback instanceof Closure
331
                ? $callback($this->response, ...(array) $parameters)
332
                : $this->response->$callback(...(array) $parameters);
333
        }
334
335
        return $default;
336 16
    }
337
338 16
    /**
339
     * Get the response content.
340
     *
341
     * @return string
342
     */
343
    public function getContent()
344
    {
345
        return (string) $this->getBody();
346
    }
347 8
348
    /**
349 8
     * Get the JSON-decoded response content.
350
     *
351
     * @param  bool  $assoc
352
     * @return mixed
353
     */
354
    public function getJson($assoc = true)
355
    {
356
        return json_decode($this->getContent(), $assoc);
357
    }
358
359
    /**
360 41
     * Make request to a URI.
361
     *
362 41
     * @param  string|\Psr\Http\Message\UriInterface  $uri
363
     * @param  string  $method
364 41
     * @param  array  $options
365 41
     * @return $this
366
     */
367
    public function request($uri = '', $method = 'GET', array $options = [])
368 41
    {
369 14
        $this->response = null;
370 4
371 4
        $method = strtoupper($method);
372
        $options = array_replace_recursive($this->options, $options);
373
374
        try {
375 41
            $this->response = $this->client->request($method, $uri, $options);
376
        } catch (Exception $e) {
377
            if (! $this->catchExceptions) {
378
                throw $e;
379
            }
380
        }
381
382
        return $this;
383
    }
384
385
    /**
386 8
     * Make request to a URI, expecting JSON content.
387
     *
388 8
     * @param  string|\Psr\Http\Message\UriInterface  $uri
389 8
     * @param  string  $method
390 2
     * @param  array  $options
391
     * @return $this
392 8
     */
393
    public function requestJson($uri = '', $method = 'GET', array $options = [])
394
    {
395
        $options = $this->addAcceptableJsonType(
396
            array_replace_recursive($this->options, $options)
397
        );
398
399
        return $this->request($uri, $method, $options);
400
    }
401 8
402
    /**
403 8
     * Add JSON type to the "Accept" header for the request options.
404
     *
405 8
     * @param  array  $options
406 8
     * @return array
407 8
     */
408 2
    protected function addAcceptableJsonType(array $options)
409
    {
410 8
        $accept = Arr::get($options, 'headers.Accept', '');
411
412
        if (! Str::contains($accept, ['/json', '+json'])) {
413
            $accept = rtrim('application/json,'.$accept, ',');
414
            Arr::set($options, 'headers.Accept', $accept);
415
        }
416
417
        return $options;
418
    }
419
420
    /**
421 4
     * Make asynchronous request to a URI.
422
     *
423 4
     * @param  string|\Psr\Http\Message\UriInterface  $uri
424
     * @param  string  $method
425
     * @param  array  $options
426
     * @return \GuzzleHttp\Promise\PromiseInterface
427
     */
428
    public function requestAsync($uri = '', $method = 'GET', array $options = [])
429
    {
430
        return $this->client->requestAsync(
431
            strtoupper($method),
432
            $uri,
433
            array_replace_recursive($this->options, $options)
434 4
        );
435
    }
436 4
437
    /**
438
     * Request the URI and return the response content.
439
     *
440
     * @param  string|\Psr\Http\Message\UriInterface  $uri
441
     * @param  string  $method
442
     * @param  array  $options
443
     * @return string
444 33
     */
445
    public function fetchContent($uri = '', $method = 'GET', array $options = [])
446
    {
447 33
        return $this->request($uri, $method, $options)->getContent();
448 9
    }
449
450
    /**
451
     * Request the URI and return the JSON-decoded response content.
452
     *
453
     * @param  string|\Psr\Http\Message\UriInterface  $uri
454
     * @param  string  $method
455
     * @param  array  $options
456
     * @return mixed
457
     */
458 5
    public function fetchJson($uri = '', $method = 'GET', array $options = [])
459
    {
460 5
        return $this->requestJson($uri, $method, $options)->getJson();
461 1
    }
462 1
463 5
    /**
464
     * Get all allowed magic request methods.
465
     *
466 5
     * @return array
467
     */
468
    protected function getMagicRequestMethods()
469
    {
470
        return [
471
            'get', 'head', 'post', 'put', 'patch', 'delete', 'options',
472
        ];
473
    }
474 28
475
    /**
476
     * Determine if the given method is a magic request method.
477 28
     *
478 7
     * @param  string  $method
479 7
     * @param  string  &$requestMethod
480
     * @param  string  &$httpMethod
481
     * @return bool
482
     */
483
    protected function isMagicRequest($method, &$requestMethod, &$httpMethod)
484
    {
485
        if (strlen($method) > 5 && $pos = strrpos($method, 'Async', -5)) {
486
            $httpMethod = substr($method, 0, $pos);
487 8
            $requestMethod = 'requestAsync';
488
        } else {
489 8
            $httpMethod = $method;
490
            $requestMethod = 'request';
491 8
        }
492 4
493 4
        if (in_array($httpMethod, $this->getMagicRequestMethods())) {
494 4
            return true;
495 4
        }
496 1
497 1
        $httpMethod = $requestMethod = null;
498
499 8
        return false;
500
    }
501
502
    /**
503
     * Get parameters for $this->request() from the magic request call.
504
     *
505
     * @param  string  $method
506
     * @param  array  $parameters
507
     * @return array
508 8
     */
509
    protected function getRequestParameters($method, array $parameters)
510 8
    {
511 4
        if (empty($parameters)) {
512
            $parameters = ['', $method];
513 4
        } else {
514
            array_splice($parameters, 1, 0, $method);
515
        }
516
517
        return $parameters;
518
    }
519
520
    /**
521
     * Get all allowed magic response methods.
522
     *
523
     * @return array
524
     */
525
    protected function getMagicResponseMethods()
526 33
    {
527
        return [
528 33
            'getStatusCode', 'getReasonPhrase', 'getProtocolVersion',
529 5
            'getHeaders', 'hasHeader', 'getHeader', 'getHeaderLine', 'getBody',
530
        ];
531
    }
532 28
533 20
    /**
534
     * Get all allowed magic option methods.
535
     *
536 8
     * @return array
537 4
     */
538 4
    protected function getMagicOptionMethods()
539
    {
540
        static $optionMethods = null;
541 4
542
        if (is_null($optionMethods)) {
543
            $reflector = new ReflectionClass(RequestOptions::class);
544 4
            $optionMethods = array_map(
545
                [Str::class, 'camel'],
546
                array_values($reflector->getConstants())
547
            );
548
        }
549
550
        return $optionMethods;
551
    }
552
553
    /**
554
     * Get the option name for the given magic option method.
555
     *
556
     * @param  string  $method
557
     * @return string|null
558
     */
559
    protected function getOptionNameForMethod($method)
560
    {
561
        if (in_array($method, $this->getMagicOptionMethods())) {
562
            return Str::snake($method);
563
        }
564
    }
565
566
    /**
567
     * Handle magic method to send request, get response data, or set
568
     * request options.
569
     *
570
     * @param  string  $method
571
     * @param  array  $parameters
572
     * @return mixed
573
     *
574
     * @throws \InvalidArgumentException
575
     * @throws \BadMethodCallException
576
     */
577
    public function __call($method, $parameters)
578
    {
579
        if ($this->isMagicRequest($method, $requestMethod, $httpMethod)) {
580
            return $this->$requestMethod(...$this->getRequestParameters($httpMethod, $parameters));
581
        }
582
583
        if (in_array($method, $this->getMagicResponseMethods())) {
584
            return $this->getResponseData($method, $parameters);
585
        }
586
587
        if ($option = $this->getOptionNameForMethod($method)) {
588
            if (empty($parameters)) {
589
                throw new InvalidArgumentException("Method [$method] needs one argument.");
590
            }
591
592
            return $this->option($option, $parameters[0]);
593
        }
594
595
        throw new BadMethodCallException("Method [$method] does not exist.");
596
    }
597
}
598