RestClient::execute()   F
last analyzed

Complexity

Conditions 21
Paths 1440

Size

Total Lines 91
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 21
eloc 55
c 1
b 0
f 0
nc 1440
nop 4
dl 0
loc 91
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace ZanySoft\LaravelSerpApi\Lib;
6
7
use ZanySoft\LaravelSerpApi\Exceptions\RestClientException;
8
9
/**
10
 * PHP REST Client
11
 */
12
class RestClient implements \Iterator, \ArrayAccess
13
{
14
    public $options;
15
    public $handle; // cURL resource handle.
16
    public $url;
17
18
    // Populated after execution:
19
    public $response; // Response body.
20
    public $headers; // Parsed reponse header object.
21
    public $info; // Response info object.
22
    public $error; // Response error string.
23
    public $response_status_lines; // indexed array of raw HTTP response status lines.
24
25
    // Populated as-needed.
26
    public $decoded_response; // Decoded response body.
27
28
    public function __construct($options = [])
29
    {
30
        $default_options = [
31
            'headers' => [],
32
            'parameters' => [],
33
            'curl_options' => [],
34
            'build_indexed_queries' => false,
35
            'user_agent' => "PHP RestClient/0.1.8",
36
            'base_url' => null,
37
            'format' => null,
38
            'format_regex' => "/(\w+)\/(\w+)(;[.+])?/",
39
            'decoders' => [
40
                'json' => 'json_decode',
41
                'php' => 'unserialize'
42
            ],
43
            'username' => null,
44
            'password' => null
45
        ];
46
47
        $this->options = array_merge($default_options, $options);
48
        if (array_key_exists('decoders', $options)) {
49
            $this->options['decoders'] = array_merge(
50
                $default_options['decoders'],
51
                $options['decoders']
52
            );
53
        }
54
    }
55
56
    /**
57
     * @param $key
58
     * @param $value
59
     * @return void
60
     */
61
    public function set_option($key, $value)
62
    {
63
        $this->options[$key] = $value;
64
    }
65
66
    /**
67
     * @param $format
68
     * @param $method
69
     * @return void
70
     */
71
    public function register_decoder($format, $method)
72
    {
73
        // Decoder callbacks must adhere to the following pattern:
74
        // array my_decoder(string $data)
75
        $this->options['decoders'][$format] = $method;
76
    }
77
78
    // Iterable methods:
79
80
    /**
81
     * @return false|mixed|void
82
     * @throws RestClientException
83
     */
84
    public function rewind()
85
    {
86
        $this->decode_response();
87
88
        return reset($this->decoded_response);
89
    }
90
91
    /**
92
     * @return false|mixed
93
     */
94
    public function current()
95
    {
96
        return current($this->decoded_response);
97
    }
98
99
    /**
100
     * @return int|mixed|string|null
101
     */
102
    public function key()
103
    {
104
        return key($this->decoded_response);
105
    }
106
107
    /**
108
     * @return false|mixed|void
109
     */
110
    public function next()
111
    {
112
        return next($this->decoded_response);
113
    }
114
115
    /**
116
     * @return bool
117
     */
118
    public function valid()
119
    {
120
        return is_array($this->decoded_response)
121
            && (key($this->decoded_response) !== null);
122
    }
123
124
    // ArrayAccess methods:
125
126
    /**
127
     * @param $key
128
     * @return bool
129
     * @throws RestClientException
130
     */
131
    public function offsetExists($key)
132
    {
133
        $this->decode_response();
134
135
        return is_array($this->decoded_response) ?
136
            isset($this->decoded_response[$key]) : isset($this->decoded_response->{$key});
137
    }
138
139
    public function offsetGet($key)
140
    {
141
        $this->decode_response();
142
        if (!$this->offsetExists($key)) {
143
            return null;
144
        }
145
146
        return is_array($this->decoded_response) ?
147
            $this->decoded_response[$key] : $this->decoded_response->{$key};
148
    }
149
150
    public function offsetSet($key, $value)
151
    {
152
        throw new RestClientException("Decoded response data is immutable.");
153
    }
154
155
    public function offsetUnset($key)
156
    {
157
        throw new RestClientException("Decoded response data is immutable.");
158
    }
159
160
    // Request methods:
161
162
    /**
163
     * @param $url
164
     * @param $parameters
165
     * @param $headers
166
     * @return $this|RestClient
167
     */
168
    public function get($url, $parameters = [], $headers = [])
169
    {
170
        return $this->execute($url, 'GET', $parameters, $headers);
171
    }
172
173
    /**
174
     * @param $url
175
     * @param $parameters
176
     * @param $headers
177
     * @return $this|RestClient
178
     */
179
    public function post($url, $parameters = [], $headers = [])
180
    {
181
        return $this->execute($url, 'POST', $parameters, $headers);
182
    }
183
184
    /**
185
     * @param $url
186
     * @param $parameters
187
     * @param $headers
188
     * @return $this|RestClient
189
     */
190
    public function put($url, $parameters = [], $headers = [])
191
    {
192
        return $this->execute($url, 'PUT', $parameters, $headers);
193
    }
194
195
    /**
196
     * @param $url
197
     * @param $parameters
198
     * @param $headers
199
     * @return $this|RestClient
200
     */
201
    public function patch($url, $parameters = [], $headers = [])
202
    {
203
        return $this->execute($url, 'PATCH', $parameters, $headers);
204
    }
205
206
    /**
207
     * @param $url
208
     * @param $parameters
209
     * @param $headers
210
     * @return $this|RestClient
211
     */
212
    public function delete($url, $parameters = [], $headers = [])
213
    {
214
        return $this->execute($url, 'DELETE', $parameters, $headers);
215
    }
216
217
    /**
218
     * @param $url
219
     * @param $parameters
220
     * @param $headers
221
     * @return $this|RestClient
222
     */
223
    public function head($url, $parameters = [], $headers = [])
224
    {
225
        return $this->execute($url, 'HEAD', $parameters, $headers);
226
    }
227
228
    /**
229
     * @param $url
230
     * @param $method
231
     * @param $parameters
232
     * @param $headers
233
     * @return $this|RestClient
234
     */
235
    public function execute($url, $method = 'GET', $parameters = [], $headers = [])
236
    {
237
        $client = clone $this;
238
        $client->url = $url;
239
        $client->handle = curl_init();
240
        $curlopt = [
241
            CURLOPT_HEADER => true,
242
            CURLOPT_RETURNTRANSFER => true,
243
            CURLOPT_USERAGENT => $client->options['user_agent'],
244
        ];
245
246
        if ($client->options['username'] && $client->options['password']) {
247
            $curlopt[CURLOPT_USERPWD] = sprintf(
248
                "%s:%s",
249
                $client->options['username'],
250
                $client->options['password']
251
            );
252
        }
253
254
        if (count($client->options['headers']) || count($headers)) {
255
            $curlopt[CURLOPT_HTTPHEADER] = [];
256
            $headers = array_merge($client->options['headers'], $headers);
257
            foreach ($headers as $key => $values) {
258
                foreach (is_array($values) ? $values : [$values] as $value) {
259
                    $curlopt[CURLOPT_HTTPHEADER][] = sprintf("%s:%s", $key, $value);
260
                }
261
            }
262
        }
263
264
        if ($client->options['format']) {
265
            $client->url .= '.' . $client->options['format'];
266
        }
267
268
        // Allow passing parameters as a pre-encoded string (or something that
269
        // allows casting to a string). Parameters passed as strings will not be
270
        // merged with parameters specified in the default options.
271
        if (is_array($parameters)) {
272
            $parameters = array_merge($client->options['parameters'], $parameters);
273
            $parameters_string = http_build_query($parameters);
274
275
            // http_build_query automatically adds an array index to repeated
276
            // parameters which is not desirable on most systems. This hack
277
            // reverts "key[0]=foo&key[1]=bar" to "key[]=foo&key[]=bar"
278
            if (!$client->options['build_indexed_queries']) {
279
                $parameters_string = preg_replace(
280
                    "/%5B[0-9]+%5D=/simU",
281
                    "%5B%5D=",
282
                    $parameters_string
283
                );
284
            }
285
        } else {
286
            $parameters_string = (string)$parameters;
287
        }
288
289
        if (strtoupper($method) == 'POST') {
290
            $curlopt[CURLOPT_POST] = true;
291
            $curlopt[CURLOPT_POSTFIELDS] = $parameters_string;
292
        } elseif (strtoupper($method) != 'GET') {
293
            $curlopt[CURLOPT_CUSTOMREQUEST] = strtoupper($method);
294
            $curlopt[CURLOPT_POSTFIELDS] = $parameters_string;
295
        } elseif ($parameters_string) {
296
            $client->url .= strpos($client->url, '?') ? '&' : '?';
297
            $client->url .= $parameters_string;
298
        }
299
300
        if ($client->options['base_url']) {
301
            if ($client->url[0] != '/' && substr($client->options['base_url'], -1) != '/') {
302
                $client->url = '/' . $client->url;
303
            }
304
            $client->url = $client->options['base_url'] . $client->url;
305
        }
306
        $curlopt[CURLOPT_URL] = $client->url;
307
308
        if ($client->options['curl_options']) {
309
            // array_merge would reset our numeric keys.
310
            foreach ($client->options['curl_options'] as $key => $value) {
311
                $curlopt[$key] = $value;
312
            }
313
        }
314
        curl_setopt_array($client->handle, $curlopt);
315
316
        $response = curl_exec($client->handle);
317
        if ($response !== false) {
318
            $client->parse_response($response);
319
        }
320
        $client->info = (object)curl_getinfo($client->handle);
321
        $client->error = curl_error($client->handle);
322
323
        curl_close($client->handle);
324
325
        return $client;
326
    }
327
328
    /**
329
     * @param $response
330
     * @return void
331
     */
332
    public function parse_response($response)
333
    {
334
        $headers = [];
335
        $this->response_status_lines = [];
336
        $line = strtok($response, "\n");
337
        do {
338
            if (strlen(trim($line)) == 0) {
339
                // Since we tokenize on \n, use the remaining \r to detect empty lines.
340
                if (count($headers) > 0) {
341
                    break;
342
                } // Must be the newline after headers, move on to response body
343
            } elseif (strpos($line, 'HTTP') === 0) {
344
                // One or more HTTP status lines
345
                $this->response_status_lines[] = trim($line);
346
            } else {
347
                // Has to be a header
348
                list($key, $value) = explode(':', $line, 2);
349
                $key = strtolower(trim(str_replace('-', '_', $key)));
350
                $value = trim($value);
351
352
                if (isset($headers[$key]) && empty($headers[$key])) {
353
                    $headers[$key] = $value;
354
                } elseif (isset($headers[$key]) && is_array($headers[$key])) {
355
                    $headers[$key][] = $value;
356
                } else {
357
                    $headers[$key] = [$headers[$key], $value];
358
                }
359
            }
360
        } while ($line = strtok("\n"));
361
362
        $this->headers = (object)$headers;
363
        $this->response = strtok("");
364
    }
365
366
    /**
367
     * @return string
368
     * @throws RestClientException
369
     */
370
    public function get_response_format()
371
    {
372
        $this->decode_response();
373
374
        if (!$this->response) {
375
            throw new RestClientException("A response must exist before it can be decoded.");
376
        }
377
378
        // User-defined format.
379
        if (!empty($this->options['format'])) {
380
            return $this->options['format'];
381
        }
382
383
        // Extract format from response content-type header.
384
        if (!empty($this->headers->content_type)) {
385
            if (preg_match($this->options['format_regex'], $this->headers->content_type, $matches)) {
386
                return $matches[2];
387
            }
388
        }
389
390
        throw new RestClientException(
391
            "Response format could not be determined."
392
        );
393
    }
394
395
    /**
396
     * @return mixed
397
     * @throws RestClientException
398
     */
399
    public function decode_response()
400
    {
401
        if (empty($this->decoded_response)) {
402
            $format = $this->get_response_format();
403
            if (!array_key_exists($format, $this->options['decoders'])) {
404
                throw new RestClientException("'${format}' is not a supported " .
405
                    "format, register a decoder to handle this response.");
406
            }
407
408
            $this->decoded_response = call_user_func(
409
                $this->options['decoders'][$format],
410
                $this->response
411
            );
412
        }
413
414
        return $this->decoded_response;
415
    }
416
}
417