Completed
Push — master ( ec9312...bbe280 )
by Elf
07:33
created

HttpClient::getResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
3
namespace ElfSundae;
4
5
use Closure;
6
use Exception;
7
use GuzzleHttp\Client;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Str;
10
use InvalidArgumentException;
11
use Psr\Http\Message\UriInterface;
12
13
class HttpClient
14
{
15
    /**
16
     * The default request options.
17
     *
18
     * @var array
19
     */
20
    protected static $defaultOptions = [
21
        'connect_timeout' => 5,
22
        'timeout' => 30,
23
    ];
24
25
    /**
26
     * The Guzzle client.
27
     *
28
     * @var \GuzzleHttp\Client
29
     */
30
    protected $client;
31
32
    /**
33
     * The request options.
34
     *
35
     * @var array
36
     */
37
    protected $options = [];
38
39
    /**
40
     * The Guzzle response.
41
     *
42
     * @var \GuzzleHttp\Psr7\Response
43
     */
44
    protected $response;
45
46
    /**
47
     * Indicate whether to catch Guzzle exceptions.
48
     *
49
     * @var bool
50
     */
51
    protected $catchExceptions = true;
52
53
    /**
54
     * Get the default request options.
55
     *
56
     * @return array
57
     */
58 24
    public static function defaultOptions()
59
    {
60 24
        return static::$defaultOptions;
61
    }
62
63
    /**
64
     * Set the default request options.
65
     *
66
     * @param  array  $options
67
     * @return void
68
     */
69 1
    public static function setDefaultOptions(array $options)
70
    {
71 1
        static::$defaultOptions = $options;
72 1
    }
73
74
    /**
75
     * Create a http client instance.
76
     *
77
     * @param  array|string|\Psr\Http\Message\UriInterface  $options  base URI or any request options
78
     *
79
     * @throws \InvalidArgumentException
80
     */
81 23
    public function __construct($options = [])
82
    {
83 23
        if (is_string($options) || $options instanceof UriInterface) {
84 1
            $options = ['base_uri' => $options];
85 23
        } elseif (! is_array($options)) {
86
            throw new InvalidArgumentException('Options must be a string, UriInterface, or an array');
87
        }
88
89 23
        $this->client = new Client(
90 23
            array_replace_recursive(static::defaultOptions(), $options)
91 23
        );
92
93 23
        $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...
94 23
    }
95
96
    /**
97
     * Get the Guzzle client instance.
98
     *
99
     * @return \GuzzleHttp\Client
100
     */
101 2
    public function getClient()
102
    {
103 2
        return $this->client;
104
    }
105
106
    /**
107
     * Get whether to catch Guzzle exceptions or not.
108
     *
109
     * @return bool
110
     */
111
    public function areExceptionsCaught()
112
    {
113
        return $this->catchExceptions;
114
    }
115
116
    /**
117
     * Set whether to catch Guzzle exceptions or not.
118
     *
119
     * @param  bool  $catch
120
     * @return $this
121
     */
122
    public function catchExceptions($catch)
123
    {
124
        $this->catchExceptions = (bool) $catch;
125
126
        return $this;
127
    }
128
129
    /**
130
     * Get the request options using "dot" notation.
131
     *
132
     * @param  string|null  $key
133
     * @return mixed
134 3
     */
135
    public function getOption($key = null)
136 3
    {
137
        return Arr::get($this->options, $key);
138
    }
139
140
    /**
141
     * Set the request options using "dot" notation.
142
     *
143
     * @param  string|array  $key
144
     * @param  mixed  $value
145
     * @return $this
146
     */
147
    public function option($key, $value = null)
148
    {
149
        $keys = is_array($key) ? $key : [$key => $value];
150
151
        foreach ($keys as $key => $value) {
152
            Arr::set($this->options, $key, $value);
153
        }
154
155
        return $this;
156
    }
157
158
    /**
159
     * Merge the given options to the request options.
160
     *
161
     * @param  array  $options,...
0 ignored issues
show
Documentation introduced by
There is no parameter named $options,.... Did you maybe mean $options?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
162
     * @return $this
163
     */
164
    public function mergeOptions(array ...$options)
165
    {
166
        $this->options = array_replace_recursive($this->options, ...$options);
167
168
        return $this;
169
    }
170
171 1
    /**
172
     * Remove one or many request options using "dot" notation.
173 1
     *
174
     * @param  array|string  $keys
175
     * @return $this
176
     */
177
    public function removeOptions($keys)
178
    {
179
        Arr::forget($this->options, is_array($keys) ? $keys : func_get_args());
180
181
        return $this;
182
    }
183 1
184
    /**
185 1
     * Set a request header.
186
     *
187 1
     * @param  string  $name
188 1
     * @param  mixed  $value
189 1
     * @return $this
190
     */
191 1
    public function header($name, $value)
192
    {
193
        return $this->option('headers.'.$name, $value);
194
    }
195
196
    /**
197
     * Set the request content type.
198
     *
199
     * @param  string  $type
200
     * @return $this
201
     */
202
    public function contentType($type)
203
    {
204
        return $this->header('Content-Type', $type);
205
    }
206
207
    /**
208
     * Set the request accept type.
209
     *
210
     * @param  string  $type
211
     * @return $this
212
     */
213
    public function accept($type)
214
    {
215
        return $this->header('Accept', $type);
216
    }
217
218
    /**
219
     * Set the request accept type to "application/json".
220
     *
221
     * @return $this
222
     */
223
    public function acceptJson()
224
    {
225
        return $this->accept('application/json');
226
    }
227
228
    /**
229
     * Specify where the body of the response will be saved.
230
     * Set the "sink" option.
231
     *
232
     * @param  mixed  $dest
233
     * @return $this
234
     */
235
    public function saveTo($dest)
236
    {
237
        return $this->removeOptions('save_to')->option('sink', $dest);
238
    }
239
240
    /**
241
     * Get the Guzzle response instance.
242
     *
243
     * @return \GuzzleHttp\Psr7\Response|null
244
     */
245
    public function getResponse()
246
    {
247
        return $this->response;
248
    }
249
250
    /**
251
     * Get data from the response.
252
     *
253
     * @param  string|\Closure  $callback
254
     * @param  array  $parameters
255
     * @param  mixed  $default
256
     * @return mixed
257
     */
258
    protected function getResponseData($callback, array $parameters = [], $default = null)
259
    {
260
        if ($this->response) {
261
            return $callback instanceof Closure
262
                ? $callback($this->response, ...$parameters)
263
                : $this->response->$callback(...$parameters);
264
        }
265
266
        return $default;
267
    }
268
269
    /**
270
     * Get the response content.
271
     *
272
     * @return string
273
     */
274
    public function getContent()
275
    {
276
        return (string) $this->getBody();
0 ignored issues
show
Documentation Bug introduced by
The method getBody does not exist on object<ElfSundae\HttpClient>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
277
    }
278
279
    /**
280
     * Get the JSON-decoded response content.
281
     *
282
     * @param  bool  $assoc
283
     * @return mixed
284
     */
285
    public function json($assoc = true)
286
    {
287
        return json_decode($this->getContent(), $assoc);
288
    }
289
290
    /**
291
     * Make request to a URI.
292
     *
293
     * @param  string  $uri
294
     * @param  string  $method
295
     * @param  array  $options
296
     * @return $this
297
     */
298
    public function request($uri = '', $method = 'GET', array $options = [])
299
    {
300
        $options = array_replace_recursive($this->options, $options);
301
302
        $this->response = null;
303
304
        try {
305
            $this->response = $this->client->request($method, $uri, $options);
306
        } catch (Exception $e) {
307
            if (! $this->catchExceptions) {
308 2
                throw $e;
309
            }
310 2
        }
311
312 2
        return $this;
313
    }
314
315 2
    /**
316 2
     * Make request to a URI, expecting JSON content.
317 2
     *
318
     * @param  string  $uri
319
     * @param  string  $method
320
     * @param  array  $options
321
     * @return $this
322 2
     */
323
    public function requestJson($uri = '', $method = 'GET', array $options = [])
324
    {
325
        $options = $this->addAcceptableJsonType(
326
            array_replace_recursive($this->options, $options)
327
        );
328
329
        return $this->request($uri, $method, $options);
330
    }
331
332
    /**
333
     * Add JSON type to the "Accept" header for the request options.
334
     *
335
     * @param  array  $options
336
     * @return array
337
     */
338
    protected function addAcceptableJsonType(array $options)
339
    {
340
        $accept = Arr::get($options, 'headers.Accept', '');
341
342
        if (! Str::contains($accept, ['/json', '+json'])) {
343
            $accept = rtrim('application/json,'.$accept, ',');
344
            Arr::set($options, 'headers.Accept', $accept);
345
        }
346
347
        return $options;
348
    }
349
350
    /**
351
     * Request the URI and return the response content.
352
     *
353
     * @param  string  $uri
354
     * @param  string  $method
355
     * @param  array  $options
356
     * @return string
357
     */
358
    public function fetchContent($uri = '', $method = 'GET', array $options = [])
359
    {
360
        return $this->request($uri, $method, $options)->getContent();
361
    }
362
363
    /**
364
     * Request the URI and return the JSON-decoded response content.
365
     *
366
     * @param  string  $uri
367
     * @param  string  $method
368
     * @param  array  $options
369
     * @return mixed
370
     */
371
    public function fetchJson($uri = '', $method = 'GET', array $options = [])
372
    {
373
        return $this->requestJson($uri, $method, $options)->json();
374
    }
375
376
    /**
377
     * Get the dynamic request methods.
378
     *
379
     * @return array
380
     */
381
    protected function getDynamicRequestMethods()
382
    {
383
        return [
384
            'get', 'head', 'put', 'post', 'patch', 'delete', 'options',
385
        ];
386
    }
387
388
    /**
389
     * Get the dynamic requestJson methods.
390
     *
391 2
     * @return array
392
     */
393
    protected function getDynamicRequestJsonMethods()
394 2
    {
395 2
        return [
396
            'get', 'put', 'post', 'patch', 'delete',
397
        ];
398
    }
399
400
    /**
401
     * Get the dynamic response methods.
402
     *
403 1
     * @return array
404
     */
405
    protected function getDynamicResponseMethods()
406 1
    {
407 1
        return [
408
            'getStatusCode', 'getReasonPhrase', 'getProtocolVersion',
409
            'getHeaders', 'hasHeader', 'getHeader', 'getHeaderLine', 'getBody',
410
        ];
411
    }
412
413
    /**
414
     * Insert HTTP method to the parameters.
415 1
     *
416
     * @param  array  $parameters
417
     * @return array
418 1
     */
419 1
    protected function insertHttpMethodToParameters($method, array $parameters)
420 1
    {
421
        $method = strtoupper($method);
422
423
        if (empty($parameters)) {
424
            $parameters = ['', $method];
425
        } else {
426
            array_splice($parameters, 1, 0, $method);
427
        }
428
429 1
        return $parameters;
430
    }
431 1
432
    /**
433 1
     * Dynamically send request, get response data, or set request option.
434
     *
435
     * @param  string  $method
436 1
     * @param  array  $parameters
437
     * @return mixed
438
     */
439 1
    public function __call($method, $parameters)
440
    {
441
        if (in_array($method, $this->getDynamicRequestMethods())) {
442
            return $this->request(
443
                ...$this->insertHttpMethodToParameters($method, $parameters)
0 ignored issues
show
Documentation introduced by
$this->insertHttpMethodT...s($method, $parameters) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
444
            );
445
        }
446
447
        if (in_array($method, $this->getDynamicRequestJsonMethods())) {
448
            return $this->requestJson(
449 2
                ...$this->insertHttpMethodToParameters($method, $parameters)
0 ignored issues
show
Documentation introduced by
$this->insertHttpMethodT...s($method, $parameters) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
450
            );
451 2
        }
452 1
453 1
        if (in_array($method, $this->getDynamicResponseMethods())) {
454 1
            return $this->getResponseData($method, $parameters);
455
        }
456
457 1
        return $this->option(Str::snake($method), ...$parameters);
458
    }
459
}
460