Completed
Push — master ( b9cb5f...251e15 )
by Elf
01:28
created

HttpClient::defaultOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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