Completed
Push — master ( c7cd2a...704807 )
by Elf
33:38 queued 24:39
created

HttpClient::accept()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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