Completed
Push — master ( 6576d2...c08ad8 )
by Elf
01:16
created

HttpClient::isMagicRequest()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 4
cts 4
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 4
nop 3
crap 4
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 mergeOption(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 removeOption($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->removeOption('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 getJsonContent($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
        $this->response = null;
301
302
        $method = strtoupper($method);
303
        $options = array_replace_recursive($this->options, $options);
304
305
        try {
306
            $this->response = $this->client->request($method, $uri, $options);
307
        } catch (Exception $e) {
308 2
            if (! $this->catchExceptions) {
309
                throw $e;
310 2
            }
311
        }
312 2
313
        return $this;
314
    }
315 2
316 2
    /**
317 2
     * Make request to a URI, expecting JSON content.
318
     *
319
     * @param  string  $uri
320
     * @param  string  $method
321
     * @param  array  $options
322 2
     * @return $this
323
     */
324
    public function requestJson($uri = '', $method = 'GET', array $options = [])
325
    {
326
        $options = $this->addAcceptableJsonType(
327
            array_replace_recursive($this->options, $options)
328
        );
329
330
        return $this->request($uri, $method, $options);
331
    }
332
333
    /**
334
     * Add JSON type to the "Accept" header for the request options.
335
     *
336
     * @param  array  $options
337
     * @return array
338
     */
339
    protected function addAcceptableJsonType(array $options)
340
    {
341
        $accept = Arr::get($options, 'headers.Accept', '');
342
343
        if (! Str::contains($accept, ['/json', '+json'])) {
344
            $accept = rtrim('application/json,'.$accept, ',');
345
            Arr::set($options, 'headers.Accept', $accept);
346
        }
347
348
        return $options;
349
    }
350
351
    /**
352
     * Request the URI and return the response content.
353
     *
354
     * @param  string  $uri
355
     * @param  string  $method
356
     * @param  array  $options
357
     * @return string
358
     */
359
    public function fetchContent($uri = '', $method = 'GET', array $options = [])
360
    {
361
        return $this->request($uri, $method, $options)->getContent();
362
    }
363
364
    /**
365
     * Request the URI and return the JSON-decoded response content.
366
     *
367
     * @param  string  $uri
368
     * @param  string  $method
369
     * @param  array  $options
370
     * @return mixed
371
     */
372
    public function fetchJson($uri = '', $method = 'GET', array $options = [])
373
    {
374
        return $this->requestJson($uri, $method, $options)->getJsonContent();
375
    }
376
377
    /**
378
     * Get all allowed magic request methods.
379
     *
380
     * @return array
381
     */
382
    protected function getMagicRequestMethods()
383
    {
384
        return [
385
            'get', 'head', 'put', 'post', 'patch', 'delete', 'options',
386
        ];
387
    }
388
389
    /**
390
     * Get all allowed magic response methods.
391 2
     *
392
     * @return array
393
     */
394 2
    protected function getMagicResponseMethods()
395 2
    {
396
        return [
397
            'getStatusCode', 'getReasonPhrase', 'getProtocolVersion',
398
            'getHeaders', 'hasHeader', 'getHeader', 'getHeaderLine', 'getBody',
399
        ];
400
    }
401
402
    /**
403 1
     * Determine if the given method is a magic request method.
404
     *
405
     * @param  string  $method
406 1
     * @param  string  &$requestMethod
407 1
     * @param  string  &$httpMethod
408
     * @return bool
409
     */
410
    protected function isMagicRequest($method, &$requestMethod, &$httpMethod)
411
    {
412
        if (strlen($method) > 4 && $pos = strrpos($method, 'Json', -4)) {
413
            $httpMethod = substr($method, 0, $pos);
414
            $requestMethod = 'requestJson';
415 1
        } else {
416
            $httpMethod = $method;
417
            $requestMethod = 'request';
418 1
        }
419 1
420 1
        if (in_array($httpMethod, $this->getMagicRequestMethods())) {
421
            return true;
422
        }
423
424
        $httpMethod = $requestMethod = null;
425
426
        return false;
427
    }
428
429 1
    /**
430
     * Get parameters for $this->request() from the magic request method.
431 1
     *
432
     * @param  string  $httpMethod
433 1
     * @param  array  $parameters
434
     * @return array
435
     */
436 1
    protected function getRequestParameters($httpMethod, array $parameters)
437
    {
438
        if (empty($parameters)) {
439 1
            $parameters = ['', $httpMethod];
440
        } else {
441
            array_splice($parameters, 1, 0, $httpMethod);
442
        }
443
444
        return $parameters;
445
    }
446
447
    /**
448
     * Handle magic method to send request, get response data, or set request options.
449 2
     *
450
     * @param  string  $method
451 2
     * @param  array  $parameters
452 1
     * @return mixed
453 1
     */
454 1
    public function __call($method, $parameters)
455
    {
456
        if ($this->isMagicRequest($method, $request, $httpMethod)) {
457 1
            return $this->{$request}(...$this->getRequestParameters($httpMethod, $parameters));
458
        }
459
460
        if (in_array($method, $this->getMagicResponseMethods())) {
461
            return $this->getResponseData($method, $parameters);
462
        }
463 1
464
        return $this->option(Str::snake($method), ...$parameters);
465
    }
466
}
467