Passed
Push — master ( 60820f...3e8c61 )
by Elf
03:05
created

HttpClient   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 497
Duplicated Lines 0 %

Test Coverage

Coverage 99.3%

Importance

Changes 0
Metric Value
wmc 55
dl 0
loc 497
ccs 141
cts 142
cp 0.993
rs 6
c 0
b 0
f 0

31 Methods

Rating   Name   Duplication   Size   Complexity  
A getClient() 0 3 1
A getOption() 0 3 1
A __construct() 0 13 4
A create() 0 3 1
A header() 0 3 1
A removeOption() 0 5 2
A option() 0 9 3
A setDefaultOptions() 0 3 1
A mergeOptions() 0 5 1
A defaultOptions() 0 3 1
A request() 0 15 3
A accept() 0 3 1
A requestAsync() 0 4 1
A multipart() 0 17 4
A areExceptionsCaught() 0 3 1
A userAgent() 0 3 1
A removeHeader() 0 9 3
A acceptJson() 0 3 1
A getRequestOptions() 0 12 1
A contentType() 0 3 1
A saveTo() 0 3 1
A requestJson() 0 5 1
A catchExceptions() 0 3 1
A isMagicOptionMethod() 0 6 2
A fetchJson() 0 4 2
A getMagicOptionMethods() 0 13 2
A isMagicRequestMethod() 0 15 4
A getRequestParameters() 0 9 2
A __call() 0 15 4
A getMagicRequestMethods() 0 4 1
A fetchContent() 0 4 2

How to fix   Complexity   

Complex Class

Complex classes like HttpClient often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HttpClient, and based on these observations, apply Extract Interface, too.

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