RestClient::execute()   F
last analyzed

Complexity

Conditions 21
Paths 864

Size

Total Lines 81
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 51
c 1
b 0
f 1
dl 0
loc 81
rs 0.1888
cc 21
nc 864
nop 4

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
/**
4
 * PHP REST Client
5
 * https://github.com/tcdent/php-restclient
6
 * (c) 2013-2017 Travis Dent <[email protected]>
7
 */
8
namespace Ballybran\Core\Http;
9
10
class RestClientException extends \Exception {}
11
12
class RestClient implements \Iterator, \ArrayAccess {
13
14
    public $options;
15
    public $handle; // cURL resource handle.
16
17
    // Populated after execution:
18
    public $response; // Response body.
19
    public $headers; // Parsed reponse header object.
20
    public $info; // Response info object.
21
    public $error; // Response error string.
22
    public $response_status_lines; // indexed array of raw HTTP response status lines.
23
24
    // Populated as-needed.
25
    public $decoded_response; // Decoded response body.
26
27
    public function __construct($options=[]){
28
        $default_options = [
29
            'headers' => [],
30
            'parameters' => [],
31
            'curl_options' => [],
32
            'build_indexed_queries' => FALSE,
33
            'user_agent' => "PHP RestClient/0.1.7",
34
            'base_url' => NULL,
35
            'format' => NULL,
36
            'format_regex' => "/(\w+)\/(\w+)(;[.+])?/",
37
            'decoders' => [
38
                'json' => 'json_decode',
39
                'php' => 'unserialize'
40
            ],
41
            'username' => NULL,
42
            'password' => NULL
43
        ];
44
45
        $this->options = array_merge($default_options, $options);
46
        if(array_key_exists('decoders', $options))
47
            $this->options['decoders'] = array_merge(
48
                $default_options['decoders'], $options['decoders']);
49
    }
50
51
    public function set_option($key, $value){
52
        $this->options[$key] = $value;
53
    }
54
55
    public function register_decoder($format, $method){
56
        // Decoder callbacks must adhere to the following pattern:
57
        //   array my_decoder(string $data)
58
        $this->options['decoders'][$format] = $method;
59
    }
60
61
    // Iterable methods:
62
    public function rewind(){
63
        $this->decode_response();
64
        return reset($this->decoded_response);
65
    }
66
67
    public function current(){
68
        return current($this->decoded_response);
69
    }
70
71
    public function key(){
72
        return key($this->decoded_response);
73
    }
74
75
    public function next(){
76
        return next($this->decoded_response);
77
    }
78
79
    public function valid(){
80
        return is_array($this->decoded_response)
81
            && (key($this->decoded_response) !== NULL);
82
    }
83
84
    // ArrayAccess methods:
85
    public function offsetExists($key){
86
        $this->decode_response();
87
        return is_array($this->decoded_response)?
88
            isset($this->decoded_response[$key]) : isset($this->decoded_response->{$key});
89
    }
90
91
    public function offsetGet($key){
92
        $this->decode_response();
93
        if(!$this->offsetExists($key))
94
            return NULL;
95
96
        return is_array($this->decoded_response)?
97
            $this->decoded_response[$key] : $this->decoded_response->{$key};
98
    }
99
100
    public function offsetSet($key, $value){
101
        throw new RestClientException("Decoded response data is immutable.");
102
    }
103
104
    public function offsetUnset($key){
105
        throw new RestClientException("Decoded response data is immutable.");
106
    }
107
108
    // Request methods:
109
    public function get($url, $parameters=[], $headers=[]){
110
        return $this->execute($url, 'GET', $parameters, $headers);
111
    }
112
113
    public function post($url, $parameters=[], $headers=[]){
114
        return $this->execute($url, 'POST', $parameters, $headers);
115
    }
116
117
    public function put($url, $parameters=[], $headers=[]){
118
        return $this->execute($url, 'PUT', $parameters, $headers);
119
    }
120
121
    public function patch($url, $parameters=[], $headers=[]){
122
        return $this->execute($url, 'PATCH', $parameters, $headers);
123
    }
124
125
    public function delete($url, $parameters=[], $headers=[]){
126
        return $this->execute($url, 'DELETE', $parameters, $headers);
127
    }
128
129
    public function head($url, $parameters=[], $headers=[]){
130
        return $this->execute($url, 'HEAD', $parameters, $headers);
131
    }
132
133
    public function execute($url, $method='GET', $parameters=[], $headers=[]){
134
        $client = clone $this;
135
        $client->url = $url;
0 ignored issues
show
Bug Best Practice introduced by
The property url does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
136
        $client->handle = curl_init();
137
        $curlopt = [
138
            CURLOPT_HEADER => TRUE,
139
            CURLOPT_RETURNTRANSFER => TRUE,
140
            CURLOPT_USERAGENT => $client->options['user_agent']
141
        ];
142
143
        if($client->options['username'] && $client->options['password'])
144
            $curlopt[CURLOPT_USERPWD] = sprintf("%s:%s",
145
                $client->options['username'], $client->options['password']);
146
147
        if(count($client->options['headers']) || count($headers)){
148
            $curlopt[CURLOPT_HTTPHEADER] = [];
149
            $headers = array_merge($client->options['headers'], $headers);
150
            foreach($headers as $key => $values){
151
                foreach(is_array($values)? $values : [$values] as $value){
152
                    $curlopt[CURLOPT_HTTPHEADER][] = sprintf("%s:%s", $key, $value);
153
                }
154
            }
155
        }
156
157
        if($client->options['format'])
158
            $client->url .= '.'.$client->options['format'];
159
160
        // Allow passing parameters as a pre-encoded string (or something that
161
        // allows casting to a string). Parameters passed as strings will not be
162
        // merged with parameters specified in the default options.
163
        if(is_array($parameters)){
164
            $parameters = array_merge($client->options['parameters'], $parameters);
165
            $parameters_string = http_build_query($parameters);
166
167
            // http_build_query automatically adds an array index to repeated
168
            // parameters which is not desirable on most systems. This hack
169
            // reverts "key[0]=foo&key[1]=bar" to "key[]=foo&key[]=bar"
170
            if(!$client->options['build_indexed_queries'])
171
                $parameters_string = preg_replace(
172
                    "/%5B[0-9]+%5D=/simU", "%5B%5D=", $parameters_string);
173
        }
174
        else
175
            $parameters_string = (string) $parameters;
176
177
        if(strtoupper($method) == 'POST'){
178
            $curlopt[CURLOPT_POST] = TRUE;
179
            $curlopt[CURLOPT_POSTFIELDS] = $parameters_string;
180
        }
181
        elseif(strtoupper($method) == 'HEAD'){
182
            $curlopt[CURLOPT_NOBODY] = TRUE;
183
        }
184
        elseif(strtoupper($method) != 'GET'){
185
            $curlopt[CURLOPT_CUSTOMREQUEST] = strtoupper($method);
186
            $curlopt[CURLOPT_POSTFIELDS] = $parameters_string;
187
        }
188
        elseif($parameters_string){
189
            $client->url .= strpos($client->url, '?')? '&' : '?';
190
            $client->url .= $parameters_string;
191
        }
192
193
        if($client->options['base_url']){
194
            if($client->url[0] != '/' && substr($client->options['base_url'], -1) != '/')
195
                $client->url = '/' . $client->url;
196
            $client->url = $client->options['base_url'] . $client->url;
197
        }
198
        $curlopt[CURLOPT_URL] = $client->url;
199
200
        if($client->options['curl_options']){
201
            // array_merge would reset our numeric keys.
202
            foreach($client->options['curl_options'] as $key => $value){
203
                $curlopt[$key] = $value;
204
            }
205
        }
206
        curl_setopt_array($client->handle, $curlopt);
207
208
        $client->parse_response(curl_exec($client->handle));
209
        $client->info = (object) curl_getinfo($client->handle);
210
        $client->error = curl_error($client->handle);
211
212
        curl_close($client->handle);
213
        return $client;
214
    }
215
216
    public function parse_response($response){
217
        $headers = [];
218
        $this->response_status_lines = [];
219
        $line = strtok($response, "\n");
220
        do {
221
            if(strlen(trim($line)) == 0){
222
                // Since we tokenize on \n, use the remaining \r to detect empty lines.
223
                if(count($headers) > 0) break; // Must be the newline after headers, move on to response body
224
            }
225
            elseif(strpos($line, 'HTTP') === 0){
226
                // One or more HTTP status lines
227
                $this->response_status_lines[] = trim($line);
228
            }
229
            else {
230
                // Has to be a header
231
                list($key, $value) = explode(':', $line, 2);
232
                $key = trim(strtolower(str_replace('-', '_', $key)));
233
                $value = trim($value);
234
235
                if(empty($headers[$key]))
236
                    $headers[$key] = $value;
237
                elseif(is_array($headers[$key]))
238
                    $headers[$key][] = $value;
239
                else
240
                    $headers[$key] = [$headers[$key], $value];
241
            }
242
        } while($line = strtok("\n"));
243
244
        $this->headers = (object) $headers;
245
        $this->response = strtok("");
246
    }
247
248
    public function get_response_format(){
249
        if(!$this->response)
250
            throw new RestClientException(
251
                "A response must exist before it can be decoded.");
252
253
        // User-defined format.
254
        if(!empty($this->options['format']))
255
            return $this->options['format'];
256
257
        // Extract format from response content-type header.
258
        if(!empty($this->headers->content_type))
259
        if(preg_match($this->options['format_regex'], $this->headers->content_type, $matches))
260
            return $matches[2];
261
262
        throw new RestClientException(
263
            "Response format could not be determined.");
264
    }
265
266
    public function decode_response(){
267
        if(empty($this->decoded_response)){
268
            $format = $this->get_response_format();
269
            if(!array_key_exists($format, $this->options['decoders']))
270
                throw new RestClientException("'${format}' is not a supported ".
271
                    "format, register a decoder to handle this response.");
272
273
            $this->decoded_response = call_user_func(
274
                $this->options['decoders'][$format], $this->response);
275
        }
276
277
        return $this->decoded_response;
278
    }
279
}
280
281