Completed
Push — master ( 0da6af...e6f8b1 )
by Elf
01:27
created

HttpClient::fetchContent()   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 3
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' => 30,
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 26
    public static function defaultOptions()
115
    {
116 26
        return static::$defaultOptions;
117
    }
118
119
    /**
120
     * Set the default request options.
121
     *
122
     * @param  array  $options
123
     * @return void
124
     */
125 27
    public static function setDefaultOptions(array $options)
126
    {
127 27
        static::$defaultOptions = $options;
128 27
    }
129
130
    /**
131
     * Create a http client instance.
132
     *
133
     * @param  array|string|\Psr\Http\Message\UriInterface  $options  base URI or any request options
134
     *
135
     * @throws \InvalidArgumentException
136
     */
137 26
    public function __construct($options = [])
138
    {
139 26
        if (is_string($options) || $options instanceof UriInterface) {
140 1
            $options = ['base_uri' => $options];
141 26
        } elseif (! is_array($options)) {
142 1
            throw new InvalidArgumentException('Options must be a string, UriInterface, or an array');
143
        }
144
145 25
        $this->client = new Client(
146 25
            array_replace_recursive(static::defaultOptions(), $options)
147 25
        );
148
149 25
        $this->options = $this->client->getConfig();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->client->getConfig() of type * is incompatible with the declared type array of property $options.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
150 25
    }
151
152
    /**
153
     * Get the Guzzle client instance.
154
     *
155
     * @return \GuzzleHttp\Client
156
     */
157 4
    public function getClient()
158
    {
159 4
        return $this->client;
160
    }
161
162
    /**
163
     * Get whether to catch Guzzle exceptions or not.
164
     *
165
     * @return bool
166
     */
167 1
    public function areExceptionsCaught()
168
    {
169 1
        return $this->catchExceptions;
170
    }
171
172
    /**
173
     * Set whether to catch Guzzle exceptions or not.
174
     *
175
     * @param  bool  $catch
176
     * @return $this
177
     */
178 2
    public function catchExceptions($catch)
179
    {
180 2
        $this->catchExceptions = (bool) $catch;
181
182 2
        return $this;
183
    }
184
185
    /**
186
     * Get the request options using "dot" notation.
187
     *
188
     * @param  string|null  $key
189
     * @return mixed
190
     */
191 11
    public function getOption($key = null)
192
    {
193 11
        return Arr::get($this->options, $key);
194
    }
195
196
    /**
197
     * Set the request options using "dot" notation.
198
     *
199
     * @param  string|array  $key
200
     * @param  mixed  $value
201
     * @return $this
202
     */
203 8
    public function option($key, $value = null)
204
    {
205 8
        $keys = is_array($key) ? $key : [$key => $value];
206
207 8
        foreach ($keys as $key => $value) {
208 8
            Arr::set($this->options, $key, $value);
209 8
        }
210
211 8
        return $this;
212
    }
213
214
    /**
215
     * Merge the given options to the request options.
216
     *
217
     * @param  array  ...$options
218
     * @return $this
219
     */
220 1
    public function mergeOptions(array ...$options)
221
    {
222 1
        $this->options = array_replace_recursive($this->options, ...$options);
223
224 1
        return $this;
225
    }
226
227
    /**
228
     * Remove one or many request options using "dot" notation.
229
     *
230
     * @param  array|string  $keys
231
     * @return $this
232
     */
233 2
    public function removeOption($keys)
234
    {
235 2
        Arr::forget($this->options, is_array($keys) ? $keys : func_get_args());
236
237 2
        return $this;
238
    }
239
240
    /**
241
     * Set a request header.
242
     *
243
     * @param  string  $name
244
     * @param  mixed  $value
245
     * @return $this
246
     */
247 5
    public function header($name, $value)
248
    {
249 5
        return $this->option('headers.'.$name, $value);
250
    }
251
252
    /**
253
     * Set the request content type.
254
     *
255
     * @param  string  $type
256
     * @return $this
257
     */
258 1
    public function contentType($type)
259
    {
260 1
        return $this->header('Content-Type', $type);
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
     * Specify where the body of the response will be saved.
286
     * Set the "sink" option.
287
     *
288
     * @param  string|resource|\Psr\Http\Message\StreamInterface  $dest
289
     * @return $this
290
     */
291 1
    public function saveTo($dest)
292
    {
293 1
        return $this->removeOption('save_to')->option('sink', $dest);
294
    }
295
296
    /**
297
     * Get the Guzzle response instance.
298
     *
299
     * @return \GuzzleHttp\Psr7\Response|null
300
     */
301 3
    public function getResponse()
302
    {
303 3
        return $this->response;
304
    }
305
306
    /**
307
     * Get data from the response.
308
     *
309
     * @param  string|\Closure  $callback
310
     * @param  array  $parameters
311
     * @param  mixed  $default
312
     * @return mixed
313
     */
314 5
    protected function getResponseData($callback, array $parameters = [], $default = null)
315
    {
316 5
        if ($this->response) {
317
            return $callback instanceof Closure
318 5
                ? $callback($this->response, ...$parameters)
319 5
                : $this->response->$callback(...$parameters);
320
        }
321
322
        return $default;
323
    }
324
325
    /**
326
     * Get the response content.
327
     *
328
     * @return string
329
     */
330 4
    public function getContent()
331
    {
332 4
        return (string) $this->getBody();
333
    }
334
335
    /**
336
     * Get the JSON-decoded response content.
337
     *
338
     * @param  bool  $assoc
339
     * @return mixed
340
     */
341 2
    public function getJsonContent($assoc = true)
342
    {
343 2
        return json_decode($this->getContent(), $assoc);
344
    }
345
346
    /**
347
     * Make request to a URI.
348
     *
349
     * @param  string|\Psr\Http\Message\UriInterface  $uri
350
     * @param  string  $method
351
     * @param  array  $options
352
     * @return $this
353
     */
354 10
    public function request($uri = '', $method = 'GET', array $options = [])
355
    {
356 10
        $this->response = null;
357
358 10
        $method = strtoupper($method);
359 10
        $options = array_replace_recursive($this->options, $options);
360
361
        try {
362 10
            $this->response = $this->client->request($method, $uri, $options);
0 ignored issues
show
Bug introduced by
It seems like $uri defined by parameter $uri on line 354 can also be of type object<Psr\Http\Message\UriInterface>; however, GuzzleHttp\Client::request() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
363 10
        } catch (Exception $e) {
364 1
            if (! $this->catchExceptions) {
365 1
                throw $e;
366
            }
367
        }
368
369 10
        return $this;
370
    }
371
372
    /**
373
     * Make request to a URI, expecting JSON content.
374
     *
375
     * @param  string|\Psr\Http\Message\UriInterface  $uri
376
     * @param  string  $method
377
     * @param  array  $options
378
     * @return $this
379
     */
380 4
    public function requestJson($uri = '', $method = 'GET', array $options = [])
381
    {
382 4
        $options = $this->addAcceptableJsonType(
383 4
            array_replace_recursive($this->options, $options)
384 4
        );
385
386 4
        return $this->request($uri, $method, $options);
387
    }
388
389
    /**
390
     * Add JSON type to the "Accept" header for the request options.
391
     *
392
     * @param  array  $options
393
     * @return array
394
     */
395 4
    protected function addAcceptableJsonType(array $options)
396
    {
397 4
        $accept = Arr::get($options, 'headers.Accept', '');
398
399 4
        if (! Str::contains($accept, ['/json', '+json'])) {
400 4
            $accept = rtrim('application/json,'.$accept, ',');
401 4
            Arr::set($options, 'headers.Accept', $accept);
402 4
        }
403
404 4
        return $options;
405
    }
406
407
    /**
408
     * Request the URI and return the response content.
409
     *
410
     * @param  string|\Psr\Http\Message\UriInterface  $uri
411
     * @param  string  $method
412
     * @param  array  $options
413
     * @return string
414
     */
415 1
    public function fetchContent($uri = '', $method = 'GET', array $options = [])
416
    {
417 1
        return $this->request($uri, $method, $options)->getContent();
418
    }
419
420
    /**
421
     * Request the URI and return the JSON-decoded response content.
422
     *
423
     * @param  string|\Psr\Http\Message\UriInterface  $uri
424
     * @param  string  $method
425
     * @param  array  $options
426
     * @return mixed
427
     */
428 1
    public function fetchJson($uri = '', $method = 'GET', array $options = [])
429
    {
430 1
        return $this->requestJson($uri, $method, $options)->getJsonContent();
431
    }
432
433
    /**
434
     * Get all allowed magic request methods.
435
     *
436
     * @return array
437
     */
438 9
    protected function getMagicRequestMethods()
439
    {
440
        return [
441 9
            'get', 'head', 'post', 'put', 'patch', 'delete', 'options',
442 9
        ];
443
    }
444
445
    /**
446
     * Determine if the given method is a magic request method.
447
     *
448
     * @param  string  $method
449
     * @param  string  &$requestMethod
450
     * @param  string  &$httpMethod
451
     * @return bool
452
     */
453 9
    protected function isMagicRequestMethod($method, &$requestMethod, &$httpMethod)
454
    {
455 9
        if (strlen($method) > 4 && $pos = strrpos($method, 'Json', -4)) {
456 2
            $httpMethod = substr($method, 0, $pos);
457 2
            $requestMethod = 'requestJson';
458 2
        } else {
459 9
            $httpMethod = $method;
460 9
            $requestMethod = 'request';
461
        }
462
463 9
        if (in_array($httpMethod, $this->getMagicRequestMethods())) {
464 2
            return true;
465
        }
466
467 7
        $httpMethod = $requestMethod = null;
468
469 7
        return false;
470
    }
471
472
    /**
473
     * Get parameters for $this->request() from the magic request methods.
474
     *
475
     * @param  string  $httpMethod
476
     * @param  array  $parameters
477
     * @return array
478
     */
479 2
    protected function getRequestParameters($httpMethod, array $parameters)
480
    {
481 2
        if (empty($parameters)) {
482 1
            $parameters = ['', $httpMethod];
483 1
        } else {
484 2
            array_splice($parameters, 1, 0, $httpMethod);
485
        }
486
487 2
        return $parameters;
488
    }
489
490
    /**
491
     * Get all allowed magic response methods.
492
     *
493
     * @return array
494
     */
495 7
    protected function getMagicResponseMethods()
496
    {
497
        return [
498 7
            'getStatusCode', 'getReasonPhrase', 'getProtocolVersion',
499 7
            'getHeaders', 'hasHeader', 'getHeader', 'getHeaderLine', 'getBody',
500 7
        ];
501
    }
502
503
    /**
504
     * Get all allowed magic option methods.
505
     *
506
     * @return array
507
     */
508 2
    protected function getMagicOptionMethods()
509
    {
510 2
        static $optionMethods = null;
511
512 2
        if (is_null($optionMethods)) {
513 1
            $reflector = new ReflectionClass(RequestOptions::class);
514 1
            $options = array_values(array_diff($reflector->getConstants(), [
515 1
                'synchronous',
516 1
            ]));
517 1
            $optionMethods = array_map([Str::class, 'camel'], $options);
518 1
        }
519
520 2
        return $optionMethods;
521
    }
522
523
    /**
524
     * Get the option key for the given magic option method.
525
     *
526
     * @param  string  $method
527
     * @return string|null
528
     */
529 2
    protected function getOptionKeyForMethod($method)
530
    {
531 2
        if (in_array($method, $this->getMagicOptionMethods())) {
532 1
            return Str::snake($method);
533
        }
534 1
    }
535
536
    /**
537
     * Handle magic method to send request, get response data, or set
538
     * request options.
539
     *
540
     * @param  string  $method
541
     * @param  array  $parameters
542
     * @return mixed
543
     *
544
     * @throws \InvalidArgumentException
545
     * @throws \BadMethodCallException
546
     */
547 9
    public function __call($method, $parameters)
548
    {
549 9
        if ($this->isMagicRequestMethod($method, $request, $httpMethod)) {
550 2
            return $this->{$request}(
551 2
                ...$this->getRequestParameters($httpMethod, $parameters)
552 2
            );
553
        }
554
555 7
        if (in_array($method, $this->getMagicResponseMethods())) {
556 5
            return $this->getResponseData($method, $parameters);
557
        }
558
559 2
        if ($option = $this->getOptionKeyForMethod($method)) {
560 1
            if (empty($parameters)) {
561 1
                throw new InvalidArgumentException("Method [$method] needs one argument.");
562
            }
563
564 1
            return $this->option($option, $parameters[0]);
565
        }
566
567 1
        throw new BadMethodCallException("Method [$method] does not exist.");
568
    }
569
}
570