Completed
Push — master ( 3ae4a2...09b4e6 )
by Elf
01:59
created

HttpClient::insertHttpMethodToParameters()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 5
cts 6
cp 0.8333
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 2
crap 2.0185
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' => 25,
23
    ];
24
25
    /**
26
     * The Guzzle client.
27
     *
28
     * @var \GuzzleHttp\Client
29
     */
30
    protected $client;
31
32
    /**
33
     * The Guzzle response.
34
     *
35
     * @var \GuzzleHttp\Psr7\Response
36
     */
37
    protected $response;
38
39
    /**
40
     * The request options.
41
     *
42
     * @var array
43
     */
44
    protected $options = [];
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 22
        } elseif (! is_array($options)) {
86
            throw new InvalidArgumentException('config must be a string, UriInterface, or an array');
87
        }
88
89 23
        $this->client = new Client(
90 23
            $this->options = $options + static::defaultOptions()
91
        );
92 23
    }
93
94
    /**
95
     * Get the Guzzle client instance.
96
     *
97
     * @return \GuzzleHttp\Client
98
     */
99 2
    public function getClient()
100
    {
101 2
        return $this->client;
102
    }
103
104
    /**
105
     * Get whether to catch Guzzle exceptions or not.
106
     *
107
     * @return bool
108
     */
109
    public function areExceptionsCaught()
110
    {
111
        return $this->catchExceptions;
112
    }
113
114
    /**
115
     * Set whether to catch Guzzle exceptions or not.
116
     *
117
     * @param  bool  $catch
118
     * @return $this
119
     */
120
    public function catchExceptions($catch)
121
    {
122
        $this->catchExceptions = (bool) $catch;
123
124
        return $this;
125
    }
126
127
    /**
128
     * Get the request options.
129
     *
130
     * @return array
131
     */
132 9
    public function getOptions()
133
    {
134 9
        return $this->options;
135
    }
136
137
    /**
138
     * Set the request options.
139
     *
140
     * @param  array  $options
141
     * @return $this
142
     */
143
    public function setOptions(array $options)
144
    {
145
        $this->options = $options;
146
147
        return $this;
148
    }
149
150
    /**
151
     * Merge the given options to the request options.
152
     *
153
     * @param  array  $options
154
     * @return $this
155
     */
156
    public function mergeOptions(array $options)
157
    {
158
        return $this->setOptions($options + $this->options);
159
    }
160
161
    /**
162
     * Remove request options using "dot" notation.
163
     *
164
     * @param  string|array|null $key
165
     * @return $this
166
     */
167 16
    public function removeOptions($key = null)
168
    {
169 16
        if (is_null($key)) {
170 16
            $this->options = [];
171
        } else {
172 1
            Arr::forget($this->options, is_array($key) ? $key : func_get_args());
173
        }
174
175 16
        return $this;
176
    }
177
178
    /**
179
     * Get a request option using "dot" notation.
180
     *
181
     * @param  string $key
182
     * @return mixed
183
     */
184 3
    public function getOption($key)
185
    {
186 3
        return Arr::get($this->options, $key);
187
    }
188
189
    /**
190
     * Set a request option using "dot" notation.
191
     *
192
     * @param  string|array  $key
193
     * @param  mixed  $value
194
     * @return $this
195
     */
196 8
    public function option($key, $value = null)
197
    {
198 8
        $keys = is_array($key) ? $key : [$key => $value];
199
200 8
        foreach ($keys as $key => $value) {
201 8
            Arr::set($this->options, $key, $value);
202
        }
203
204 8
        return $this;
205
    }
206
207
    /**
208
     * Set the request header.
209
     *
210
     * @param  string  $name
211
     * @param  mixed  $value
212
     * @return $this
213
     */
214 4
    public function header($name, $value)
215
    {
216 4
        return $this->option('headers.'.$name, $value);
217
    }
218
219
    /**
220
     * Set the request content type.
221
     *
222
     * @param  string  $type
223
     * @return $this
224
     */
225 1
    public function contentType($type)
226
    {
227 1
        return $this->header('Content-Type', $type);
228
    }
229
230
    /**
231
     * Set the request accept type.
232
     *
233
     * @param  string  $type
234
     * @return $this
235
     */
236 2
    public function accept($type)
237
    {
238 2
        return $this->header('Accept', $type);
239
    }
240
241
    /**
242
     * Set the request accept type to JSON.
243
     *
244
     * @return $this
245
     */
246 1
    public function acceptJson()
247
    {
248 1
        return $this->accept('application/json');
249
    }
250
251
    /**
252
     * Specify where the body of a response will be saved.
253
     * Set the "sink" option.
254
     *
255
     * @param  mixed  $dest
256
     * @return $this
257
     */
258 1
    public function saveTo($dest)
259
    {
260 1
        return $this->removeOptions('save_to')->option('sink', $dest);
261
    }
262
263
    /**
264
     * Get the Guzzle response instance.
265
     *
266
     * @return \GuzzleHttp\Psr7\Response|null
267
     */
268 8
    public function getResponse()
269
    {
270 8
        return $this->response;
271
    }
272
273
    /**
274
     * Get data from the response.
275
     *
276
     * @param  string|\Closure  $callback
277
     * @param  array  $parameters
278
     * @param  mixed  $default
279
     * @return mixed
280
     */
281 2
    protected function getResponseData($callback, array $parameters = [], $default = null)
282
    {
283 2
        if ($this->response) {
284 2
            return $callback instanceof Closure
285
                ? $callback($this->response, ...$parameters)
286 2
                : $this->response->$callback(...$parameters);
287
        }
288
289
        return $default;
290
    }
291
292
    /**
293
     * Get the response content.
294
     *
295
     * @return string
296
     */
297 2
    public function getContent()
298
    {
299 2
        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...
300
    }
301
302
    /**
303
     * Get the JSON-decoded response content.
304
     *
305
     * @param  bool  $assoc
306
     * @return mixed
307
     */
308 1
    public function json($assoc = true)
309
    {
310 1
        return json_decode($this->getContent(), $assoc);
311
    }
312
313
    /**
314
     * Make request to a URI.
315
     *
316
     * @param  string  $uri
317
     * @param  string  $method
318
     * @param  array  $options
319
     * @return $this
320
     */
321 11
    public function request($uri = '', $method = 'GET', array $options = [])
322
    {
323 11
        $options += $this->options;
324
325 11
        $this->response = null;
326
327
        try {
328 11
            $this->response = $this->client->request($method, $uri, $options);
329 5
        } catch (Exception $e) {
330 5
            if (! $this->catchExceptions) {
331
                throw $e;
332
            }
333
        }
334
335 11
        return $this;
336
    }
337
338
    /**
339
     * Make request to a URI, expecting JSON content.
340
     *
341
     * @param  string  $uri
342
     * @param  string  $method
343
     * @param  array  $options
344
     * @return $this
345
     */
346 3
    public function requestJson($uri = '', $method = 'GET', array $options = [])
347
    {
348 3
        $options = $this->addAcceptableJsonType($options + $this->options);
349
350 3
        return $this->request($uri, $method, $options);
351
    }
352
353
    /**
354
     * Add JSON type to the "Accept" header for the request options.
355
     *
356
     * @param  array  $options
357
     * @return array
358
     */
359 3
    protected function addAcceptableJsonType(array $options)
360
    {
361 3
        $accept = Arr::get($options, 'headers.Accept', '');
362
363 3
        if (! Str::contains($accept, ['/json', '+json'])) {
364 3
            $accept = rtrim('application/json, '.$accept, ', ');
365 3
            Arr::set($options, 'headers.Accept', $accept);
366
        }
367
368 3
        return $options;
369
    }
370
371
    /**
372
     * Request the URI and return the response content.
373
     *
374
     * @param  string  $uri
375
     * @param  string  $method
376
     * @param  array  $options
377
     * @return string
378
     */
379 1
    public function fetchContent($uri = '', $method = 'GET', array $options = [])
380
    {
381 1
        return $this->request($uri, $method, $options)->getContent();
382
    }
383
384
    /**
385
     * Request the URI and return the JSON-decoded response content.
386
     *
387
     * @param  string  $uri
388
     * @param  string  $method
389
     * @param  array  $options
390
     * @return mixed
391
     */
392 1
    public function fetchJson($uri = '', $method = 'GET', array $options = [])
393
    {
394 1
        return $this->requestJson($uri, $method, $options)->json();
395
    }
396
397
    /**
398
     * Get the dynamic request methods.
399
     *
400
     * @return array
401
     */
402 8
    protected function getDynamicRequestMethods()
403
    {
404
        return [
405 8
            'get', 'head', 'put', 'post', 'patch', 'delete', 'options',
406
        ];
407
    }
408
409
    /**
410
     * Get the dynamic requestJson methods.
411
     *
412
     * @return array
413
     */
414 4
    protected function getDynamicRequestJsonMethods()
415
    {
416
        return [
417 4
            'get', 'put', 'post', 'patch', 'delete',
418
        ];
419
    }
420
421
    /**
422
     * Get the dynamic response methods.
423
     *
424
     * @return array
425
     */
426 4
    protected function getDynamicResponseMethods()
427
    {
428
        return [
429 4
            'getStatusCode', 'getReasonPhrase', 'getProtocolVersion',
430
            'getHeaders', 'hasHeader', 'getHeader', 'getHeaderLine', 'getBody',
431
        ];
432
    }
433
434
    /**
435
     * Insert HTTP method to the parameters.
436
     *
437
     * @param  array  $parameters
438
     * @return array
439
     */
440 4
    protected function insertHttpMethodToParameters($method, array $parameters)
441
    {
442 4
        $method = strtoupper($method);
443
444 4
        if (empty($parameters)) {
445
            $parameters = ['', $method];
446
        } else {
447 4
            array_splice($parameters, 1, 0, $method);
448
        }
449
450 4
        return $parameters;
451
    }
452
453
    /**
454
     * Dynamically send request, get response data, or set request option.
455
     *
456
     * @param  string  $method
457
     * @param  array  $parameters
458
     * @return mixed
459
     */
460 8
    public function __call($method, $parameters)
461
    {
462 8
        if (in_array($method, $this->getDynamicRequestMethods())) {
463 4
            return $this->request(
464 4
                ...$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...
465
            );
466
        }
467
468 4
        if (in_array($method, $this->getDynamicRequestJsonMethods())) {
469
            return $this->requestJson(
470
                ...$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...
471
            );
472
        }
473
474 4
        if (in_array($method, $this->getDynamicResponseMethods())) {
475 2
            return $this->getResponseData($method, $parameters);
476
        }
477
478 2
        return $this->option(Str::snake($method), $parameters[0]);
479
    }
480
}
481