Passed
Push — master ( fe9d5b...e13f96 )
by Elf
02:00
created

HttpClient::userAgent()   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 Exception;
6
use ReflectionClass;
7
use GuzzleHttp\Client;
8
use BadMethodCallException;
9
use Illuminate\Support\Arr;
10
use Illuminate\Support\Str;
11
use InvalidArgumentException;
12
use GuzzleHttp\RequestOptions;
13
use Psr\Http\Message\UriInterface;
14
15
/**
16
 * @method \Psr\Http\Message\ResponseInterface|null get(string|UriInterface $uri = '', array $options = [])
17
 * @method \Psr\Http\Message\ResponseInterface|null head(string|UriInterface $uri = '', array $options = [])
18
 * @method \Psr\Http\Message\ResponseInterface|null post(string|UriInterface $uri = '', array $options = [])
19
 * @method \Psr\Http\Message\ResponseInterface|null put(string|UriInterface $uri = '', array $options = [])
20
 * @method \Psr\Http\Message\ResponseInterface|null patch(string|UriInterface $uri = '', array $options = [])
21
 * @method \Psr\Http\Message\ResponseInterface|null delete(string|UriInterface $uri = '', array $options = [])
22
 * @method \Psr\Http\Message\ResponseInterface|null options(string|UriInterface $uri = '', array $options = [])
23
 * @method mixed getJson(string|UriInterface $uri = '', array $options = [])
24
 * @method mixed postJson(string|UriInterface $uri = '', array $options = [])
25
 * @method mixed putJson(string|UriInterface $uri = '', array $options = [])
26
 * @method mixed patchJson(string|UriInterface $uri = '', array $options = [])
27
 * @method mixed deleteJson(string|UriInterface $uri = '', array $options = [])
28
 * @method \GuzzleHttp\Promise\PromiseInterface getAsync(string|UriInterface $uri = '', array $options = [])
29
 * @method \GuzzleHttp\Promise\PromiseInterface headAsync(string|UriInterface $uri = '', array $options = [])
30
 * @method \GuzzleHttp\Promise\PromiseInterface postAsync(string|UriInterface $uri = '', array $options = [])
31
 * @method \GuzzleHttp\Promise\PromiseInterface putAsync(string|UriInterface $uri = '', array $options = [])
32
 * @method \GuzzleHttp\Promise\PromiseInterface patchAsync(string|UriInterface $uri = '', array $options = [])
33
 * @method \GuzzleHttp\Promise\PromiseInterface deleteAsync(string|UriInterface $uri = '', array $options = [])
34
 * @method \GuzzleHttp\Promise\PromiseInterface optionsAsync(string|UriInterface $uri = '', array $options = [])
35
 * @method $this allowRedirects(bool|array $value)
36
 * @method $this auth(array|string|null $value)
37
 * @method $this body(string|resource|\Psr\Http\Message\StreamInterface $value)
38
 * @method $this cert(string|array $value)
39
 * @method $this cookies(bool|\GuzzleHttp\Cookie\CookieJarInterface $value)
40
 * @method $this connectTimeout(float $value)
41
 * @method $this debug(bool|resource $value)
42
 * @method $this decodeContent(string|bool $value)
43
 * @method $this delay(int|float $value)
44
 * @method $this expect(bool|int $value)
45
 * @method $this forceIpResolve(string $value)
46
 * @method $this formParams(array $value)
47
 * @method $this headers(array $value)
48
 * @method $this httpErrors(bool $value)
49
 * @method $this json(mixed $value)
50
 * @method $this onHeaders(callable $value)
51
 * @method $this onStats(callable $value)
52
 * @method $this progress(callable $value)
53
 * @method $this proxy(string|array $value)
54
 * @method $this query(array|string $value)
55
 * @method $this readTimeout(float $value)
56
 * @method $this sink(string|resource|\Psr\Http\Message\StreamInterface $value)
57
 * @method $this sslKey(string|array $value)
58
 * @method $this stream(bool $value)
59
 * @method $this verify(bool|string $value)
60
 * @method $this timeout(float $value)
61
 * @method $this version(float|string $value)
62
 *
63
 * @see http://docs.guzzlephp.org/en/stable/request-options.html Request Options
64
 */
65
class HttpClient
66
{
67
    /**
68
     * The default request options.
69
     *
70
     * @var array
71
     */
72
    protected static $defaultOptions = [
73
        'catch_exceptions' => true,
74
        'http_errors' => false,
75
        'connect_timeout' => 5,
76
        'timeout' => 20,
77
    ];
78
79
    /**
80
     * The Guzzle client.
81
     *
82
     * @var \GuzzleHttp\Client
83
     */
84
    protected $client;
85
86
    /**
87
     * The request options.
88
     *
89
     * @var array
90
     */
91
    protected $options = [];
92
93
    /**
94
     * All allowed magic request methods (verbs).
95
     *
96
     * @var array
97
     */
98
    protected $magicRequestMethods = [
99
        'get', 'head', 'post', 'put', 'patch', 'delete', 'options',
100
    ];
101
102
    /**
103
     * Get the default request options.
104
     *
105
     * @return array
106
     */
107 108
    public static function defaultOptions()
108
    {
109 108
        return static::$defaultOptions;
110
    }
111
112
    /**
113
     * Set the default request options.
114
     *
115
     * @param  array  $options
116
     * @return void
117
     */
118 112
    public static function setDefaultOptions(array $options)
119
    {
120 112
        static::$defaultOptions = $options;
121 112
    }
122
123
    /**
124
     * Create a new HTTP client instance.
125
     *
126
     * @param  array|string|\Psr\Http\Message\UriInterface  $options  base URI or any request options
127
     * @return static
128
     */
129 8
    public static function create($options = [])
130 2
    {
131 8
        return new static($options);
132
    }
133
134
    /**
135
     * Create a new HTTP client instance.
136
     *
137
     * @param  array|string|\Psr\Http\Message\UriInterface  $options  base URI or any request options
138
     *
139
     * @throws \InvalidArgumentException
140
     */
141 112
    public function __construct($options = [])
142
    {
143 112
        if (is_string($options) || $options instanceof UriInterface) {
144 4
            $options = ['base_uri' => $options];
145 110
        } elseif (! is_array($options)) {
0 ignored issues
show
introduced by
The condition ! is_array($options) can never be true.
Loading history...
146 4
            throw new InvalidArgumentException('Options must be a string, UriInterface, or an array');
147
        }
148
149 108
        $this->client = new Client(
150 108
            array_replace_recursive(static::defaultOptions(), $options)
151 54
        );
152
153 108
        $this->options = $this->client->getConfig();
154 108
    }
155
156
    /**
157
     * Get the Guzzle client instance.
158
     *
159
     * @return \GuzzleHttp\Client
160
     */
161 20
    public function getClient()
162
    {
163 20
        return $this->client;
164
    }
165
166
    /**
167
     * Get the request options using "dot" notation.
168
     *
169
     * @param  string|null  $key
170
     * @param  mixed  $default
171
     * @return mixed
172
     */
173 76
    public function getOption($key = null, $default = null)
174
    {
175 76
        return Arr::get($this->options, $key, $default);
176
    }
177
178
    /**
179
     * Set the request options using "dot" notation.
180
     *
181
     * @param  string|array  $key
182
     * @param  mixed  $value
183
     * @return $this
184
     */
185 60
    public function option($key, $value = null)
186
    {
187 60
        $keys = is_array($key) ? $key : [$key => $value];
188
189 60
        foreach ($keys as $key => $value) {
190 60
            Arr::set($this->options, $key, $value);
191 30
        }
192
193 60
        return $this;
194
    }
195
196
    /**
197
     * Merge the given options to the request options.
198
     *
199
     * @param  array  $options
200
     * @return $this
201
     */
202 4
    public function mergeOptions(array $options)
203
    {
204 4
        $this->options = array_replace_recursive($this->options, $options);
205
206 4
        return $this;
207
    }
208
209
    /**
210
     * Remove one or many request options using "dot" notation.
211
     *
212
     * @param  array|string  $keys
213
     * @return $this
214
     */
215 36
    public function removeOption($keys)
216
    {
217 36
        Arr::forget($this->options, is_array($keys) ? $keys : func_get_args());
218
219 36
        return $this;
220
    }
221
222
    /**
223
     * Set a request header.
224
     *
225
     * @param  string  $name
226
     * @param  mixed  $value
227
     * @return $this
228
     */
229 20
    public function header($name, $value)
230
    {
231 20
        return $this->option('headers.'.$name, $value);
232
    }
233
234
    /**
235
     * Remove one or many request headers.
236
     *
237
     * @param  array|string  $names
238
     * @return $this
239
     */
240 4
    public function removeHeader($names)
241
    {
242 4
        if (is_array($headers = $this->getOption('headers'))) {
243 4
            $names = is_array($names) ? $names : func_get_args();
244
245 4
            $this->option('headers', Arr::except($headers, $names));
246 2
        }
247
248 4
        return $this;
249
    }
250
251
    /**
252
     * Set the request accept type.
253
     *
254
     * @param  string  $type
255
     * @return $this
256
     */
257 8
    public function accept($type)
258
    {
259 8
        return $this->header('Accept', $type);
260
    }
261
262
    /**
263
     * Set the request accept type to "application/json".
264
     *
265
     * @return $this
266
     */
267 4
    public function acceptJson()
268
    {
269 4
        return $this->accept('application/json');
270
    }
271
272
    /**
273
     * Set user agent for the request.
274
     *
275
     * @param  string  $value
276
     * @return $this
277
     */
278 4
    public function userAgent($value)
279
    {
280 4
        return $this->header('User-Agent', $value);
281
    }
282
283
    /**
284
     * Set the request content type.
285
     *
286
     * @param  string  $type
287
     * @return $this
288
     */
289 4
    public function contentType($type)
290
    {
291 4
        return $this->header('Content-Type', $type);
292
    }
293
294
    /**
295
     * Specify where the body of the response will be saved.
296
     * Set the "sink" option.
297
     *
298
     * @param  string|resource|\Psr\Http\Message\StreamInterface  $dest
299
     * @return $this
300
     */
301 4
    public function saveTo($dest)
302
    {
303 4
        return $this->option('sink', $dest);
304
    }
305
306
    /**
307
     * Set the body of the request to a multipart/form-data form.
308
     *
309
     * @param  array  $data
310
     * @return $this
311
     */
312 4
    public function multipart(array $data)
313
    {
314 4
        $multipart = [];
315
316 4
        foreach ($data as $key => $value) {
317 4
            if (! is_array($value)) {
318 4
                $value = ['contents' => $value];
319 2
            }
320
321 4
            if (! is_int($key)) {
322 4
                $value['name'] = $key;
323 2
            }
324
325 4
            $multipart[] = $value;
326 2
        }
327
328 4
        return $this->option('multipart', $multipart);
329
    }
330
331
    /**
332
     * Determine whether to catch Guzzle exceptions.
333
     *
334
     * @return bool
335
     */
336 16
    public function areExceptionsCaught()
337
    {
338 16
        return $this->getOption('catch_exceptions', false);
339
    }
340
341
    /**
342
     * Set whether to catch Guzzle exceptions or not.
343
     *
344
     * @param  bool  $catch
345
     * @return $this
346
     */
347 16
    public function catchExceptions($catch)
348
    {
349 16
        return $this->option('catch_exceptions', (bool) $catch);
350
    }
351
352
    /**
353
     * Send request to a URI.
354
     *
355
     * @param  string|\Psr\Http\Message\UriInterface  $uri
356
     * @param  string  $method
357
     * @param  array  $options
358
     * @return \Psr\Http\Message\ResponseInterface|null
359
     */
360 28
    public function request($uri = '', $method = 'GET', array $options = [])
361
    {
362
        try {
363 28
            return $this->client->request(
364 28
                $method, $uri, $this->getRequestOptions($options)
365 14
            );
366 12
        } catch (Exception $e) {
367 12
            if (! $this->areExceptionsCaught()) {
368 12
                throw $e;
369
            }
370
        }
371 12
    }
372
373
    /**
374
     * Send request to a URI, expecting JSON content.
375
     *
376
     * @param  string|\Psr\Http\Message\UriInterface  $uri
377
     * @param  string  $method
378
     * @param  array  $options
379
     * @return \Psr\Http\Message\ResponseInterface|null
380
     */
381 12
    public function requestJson($uri = '', $method = 'GET', array $options = [])
382
    {
383 12
        Arr::set($options, 'headers.Accept', 'application/json');
384
385 12
        return $this->request($uri, $method, $options);
386
    }
387
388
    /**
389
     * Send asynchronous request to a URI.
390
     *
391
     * @param  string|\Psr\Http\Message\UriInterface  $uri
392
     * @param  string  $method
393
     * @param  array  $options
394
     * @return \GuzzleHttp\Promise\PromiseInterface
395
     */
396 8
    public function requestAsync($uri = '', $method = 'GET', array $options = [])
397
    {
398 8
        return $this->client->requestAsync(
399 8
            $method, $uri, $this->getRequestOptions($options)
400 4
        );
401
    }
402
403
    /**
404
     * Get options for the Guzzle request method.
405
     *
406
     * @param  array  $options
407
     * @return array
408
     */
409 32
    protected function getRequestOptions(array $options = [])
410
    {
411 32
        $options = array_replace_recursive($this->options, $options);
412
413 32
        $this->removeOption([
414 32
            'body', 'form_params', 'multipart', 'json', 'query',
415 16
            'sink', 'save_to', 'stream',
416 16
            'on_headers', 'on_stats', 'progress',
417 16
            'headers.Content-Type',
418 16
        ]);
419
420 32
        return $options;
421
    }
422
423
    /**
424
     * Request the URI and return the response content.
425
     *
426
     * @param  string|\Psr\Http\Message\UriInterface  $uri
427
     * @param  string  $method
428
     * @param  array  $options
429
     * @return string|null
430
     */
431 4
    public function fetchContent($uri = '', $method = 'GET', array $options = [])
432
    {
433 4
        if ($response = $this->request($uri, $method, $options)) {
434 4
            return (string) $response->getBody();
435
        }
436 4
    }
437
438
    /**
439
     * Request the URI and return the JSON-decoded response content.
440
     *
441
     * @param  string|\Psr\Http\Message\UriInterface  $uri
442
     * @param  string  $method
443
     * @param  array  $options
444
     * @return mixed
445
     */
446 8
    public function fetchJson($uri = '', $method = 'GET', array $options = [])
447
    {
448 8
        if ($response = $this->requestJson($uri, $method, $options)) {
449 8
            return json_decode($response->getBody(), true);
450
        }
451 4
    }
452
453
    /**
454
     * Determine if the given method is a magic request method.
455
     *
456
     * @param  string  $method
457
     * @param  string  &$requestMethod
458
     * @param  string  &$httpMethod
459
     * @return bool
460
     */
461 16
    protected function isMagicRequestMethod($method, &$requestMethod, &$httpMethod)
462
    {
463 16
        $requestMethod = $httpMethod = null;
464
465 16
        foreach ($this->magicRequestMethods as $verb) {
466 16
            if ($method == $verb) {
467 8
                $requestMethod = 'request';
468 16
            } elseif ($method == $verb.'Async') {
469 4
                $requestMethod = 'requestAsync';
470 16
            } elseif ($method == $verb.'Json') {
471 4
                $requestMethod = 'fetchJson';
472 2
            }
473
474 16
            if ($requestMethod) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $requestMethod of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
475 12
                return (bool) $httpMethod = $verb;
476
            }
477 8
        }
478
479 8
        return false;
480
    }
481
482
    /**
483
     * Get parameters for the request() method from the magic request call.
484
     *
485
     * @param  string  $method
486
     * @param  array  $parameters
487
     * @return array
488
     */
489 8
    protected function getRequestParameters($method, array $parameters)
490
    {
491 8
        if (empty($parameters)) {
492 4
            $parameters = ['', $method];
493 2
        } else {
494 8
            array_splice($parameters, 1, 0, $method);
495
        }
496
497 8
        return $parameters;
498
    }
499
500
    /**
501
     * Get all allowed magic option methods.
502
     *
503
     * @return array
504
     */
505 8
    protected function getMagicOptionMethods()
506
    {
507 8
        static $optionMethods = null;
508
509 8
        if (is_null($optionMethods)) {
1 ignored issue
show
introduced by
The condition is_null($optionMethods) can never be false.
Loading history...
510 4
            $reflector = new ReflectionClass(RequestOptions::class);
511 4
            $optionMethods = array_map(
512 4
                [Str::class, 'camel'],
513 4
                array_values($reflector->getConstants())
514 2
            );
515 2
        }
516
517 8
        return $optionMethods;
518
    }
519
520
    /**
521
     * Determine if the given method is a magic option method.
522
     *
523
     * @param  string  $method
524
     * @return bool
525
     */
526 8
    protected function isMagicOptionMethod($method, &$option)
527
    {
528 8
        $option = in_array($method, $this->getMagicOptionMethods())
529 8
            ? Str::snake($method) : null;
530
531 8
        return (bool) $option;
532
    }
533
534
    /**
535
     * Handle magic option/request methods.
536
     *
537
     * @param  string  $method
538
     * @param  array  $parameters
539
     * @return mixed
540
     *
541
     * @throws \InvalidArgumentException
542
     * @throws \BadMethodCallException
543
     */
544 16
    public function __call($method, $parameters)
545
    {
546 16
        if ($this->isMagicRequestMethod($method, $requestMethod, $httpMethod)) {
547 8
            return $this->$requestMethod(...$this->getRequestParameters($httpMethod, $parameters));
548
        }
549
550 8
        if ($this->isMagicOptionMethod($method, $option)) {
551 4
            if (empty($parameters)) {
552 4
                throw new InvalidArgumentException("Method [$method] needs one argument.");
553
            }
554
555 4
            return $this->option($option, $parameters[0]);
556
        }
557
558 4
        throw new BadMethodCallException("Method [$method] does not exist.");
559
    }
560
}
561