Completed
Push — master ( 3c02c7...0da6af )
by Elf
01:25
created

HttpClient::isMagicRequestMethod()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

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