Completed
Push — master ( 80c9c7...4d09b7 )
by Elf
04:36
created

HttpClient::getRequestOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 1
rs 9.4285
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
        'catch_exceptions' => true,
79
        'http_errors' => false,
80
        'connect_timeout' => 5,
81
        'timeout' => 20,
82
    ];
83
84
    /**
85
     * The Guzzle client.
86
     *
87
     * @var \GuzzleHttp\Client
88
     */
89
    protected $client;
90
91
    /**
92
     * The request options.
93
     *
94
     * @var array
95
     */
96
    protected $options = [];
97
98
    /**
99
     * The Guzzle response.
100
     *
101
     * @var \GuzzleHttp\Psr7\Response
102
     */
103
    protected $response;
104
105
    /**
106
     * Get the default request options.
107
     *
108
     * @return array
109
     */
110 124
    public static function defaultOptions()
111
    {
112 124
        return static::$defaultOptions;
113
    }
114
115
    /**
116
     * Set the default request options.
117
     *
118
     * @param  array  $options
119
     * @return void
120
     */
121 124
    public static function setDefaultOptions(array $options)
122
    {
123 124
        static::$defaultOptions = $options;
124 124
    }
125
126
    /**
127
     * Create a new HTTP client instance.
128
     *
129
     * @param  array|string|\Psr\Http\Message\UriInterface  $options  base URI or any request options
130
     * @return static
131
     */
132 8
    public static function create($options = [])
133
    {
134 8
        return new static($options);
135
    }
136
137
    /**
138
     * Create a new HTTP client instance.
139
     *
140
     * @param  array|string|\Psr\Http\Message\UriInterface  $options  base URI or any request options
141
     *
142
     * @throws \InvalidArgumentException
143
     */
144 128
    public function __construct($options = [])
145
    {
146 128
        if (is_string($options) || $options instanceof UriInterface) {
147 8
            $options = ['base_uri' => $options];
148 126
        } elseif (! is_array($options)) {
149 4
            throw new InvalidArgumentException('Options must be a string, UriInterface, or an array');
150
        }
151
152 124
        $this->client = new Client(
153 124
            $this->getMergedOptions(static::defaultOptions(), $options)
154 62
        );
155
156 124
        $this->options = $this->client->getConfig();
157 124
    }
158
159
    /**
160
     * Get merged given request options.
161
     *
162
     * @param  array  ...$options
163
     * @return array
164
     */
165 124
    protected function getMergedOptions(...$options)
166
    {
167 124
        return array_replace_recursive(...$options);
0 ignored issues
show
Bug introduced by
$options is expanded, but the parameter $array of array_replace_recursive() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

167
        return array_replace_recursive(/** @scrutinizer ignore-type */ ...$options);
Loading history...
Bug introduced by
The call to array_replace_recursive() has too few arguments starting with array1. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

167
        return /** @scrutinizer ignore-call */ array_replace_recursive(...$options);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

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