Passed
Push — main ( a3fefa...605794 )
by Dylan
01:51
created

Curl   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 310
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 120
dl 0
loc 310
rs 3.44
c 2
b 0
f 0
wmc 62

18 Methods

Rating   Name   Duplication   Size   Complexity  
A setMethod() 0 8 2
A setURL() 0 4 1
A setIsFileUpload() 0 5 1
A __construct() 0 5 4
A cacheRequests() 0 4 1
A removeHeader() 0 4 2
A getHeaders() 0 3 1
A getMethod() 0 3 1
A curl_json() 0 4 1
A addDataParam() 0 4 1
A getURL() 0 3 1
A addHeader() 0 4 1
A getDataParams() 0 3 1
A isFileUpload() 0 3 1
C curl() 0 57 15
A removeDataParam() 0 4 2
B is_absolute_url() 0 23 7
F setGetVar() 0 51 19

How to fix   Complexity   

Complex Class

Complex classes like Curl often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Curl, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Lifeboat\SDK\Services;
4
5
use Lifeboat\SDK\CurlResponse;
6
use Lifeboat\SDK\Exceptions\InvalidArgumentException;
7
use LogicException;
8
9
/**
10
 * Class Curl
11
 * @package Lifeboat\SDK
12
 */
13
class Curl {
14
15
    const ALLOWED_METHODS   = ['GET', 'POST', 'DELETE', 'PUT'];
16
    const USER_AGENT        = 'LifeboatSDK/curl-service';
17
18
    private static $_cache = [];
19
20
    private $_method    = 'GET';
21
    private $_url       = '';
22
    private $_data      = [];
23
    private $_isfile    = false;
24
    private $_headers   = [
25
        'Content-Type'      => 'application/x-www-form-urlencoded',
26
        'X-Requested-By'    => self::USER_AGENT
27
    ];
28
    private $_enable_cache = false;
29
30
    /**
31
     * Curl constructor.
32
     *
33
     * @param string $url
34
     * @param array $data
35
     * @param array $headers
36
     *
37
     * @throws LogicException
38
     */
39
    public function __construct(string $url = null, array $data = [], array $headers = [])
40
    {
41
        if (!is_null($url)) $this->setURL($url);
42
        foreach ($data as $name => $value)      $this->addDataParam($name, $value);
43
        foreach ($headers as $name => $value)   $this->addHeader($name, $value);
44
    }
45
46
    /**
47
     * @param string $url
48
     * @return Curl
49
     */
50
    public function setURL(string $url): Curl
51
    {
52
        $this->_url = $url;
53
        return $this;
54
    }
55
56
    /**
57
     * @return string
58
     */
59
    public function getURL(): string
60
    {
61
        return $this->_url;
62
    }
63
64
    /**
65
     * @param string $method
66
     * @return $this
67
     * @throws InvalidArgumentException If $method specified is invalid
68
     */
69
    public function setMethod(string $method = 'GET'): Curl
70
    {
71
        if (!in_array($method, self::ALLOWED_METHODS)) {
72
            throw new InvalidArgumentException("HTTP Method '{$method}' is not allowed");
73
        }
74
75
        $this->_method = $method;
76
        return $this;
77
    }
78
79
    /**
80
     * @return string
81
     */
82
    public function getMethod(): string
83
    {
84
        return $this->_method;
85
    }
86
87
    /**
88
     * @param string $name
89
     * @param mixed $value
90
     * @return $this
91
     */
92
    public function addDataParam(string $name, $value): Curl
93
    {
94
        $this->_data[$name] = $value;
95
        return $this;
96
    }
97
98
    public function removeDataParam(string $name): Curl
99
    {
100
        if (array_key_exists($name, $this->_data)) unset($this->_data[$name]);
101
        return $this;
102
    }
103
104
    /**
105
     * @return array
106
     */
107
    public function getDataParams(): array
108
    {
109
        return $this->_data;
110
    }
111
112
    /**
113
     * @param string $name
114
     * @param string $value
115
     * @return Curl
116
     */
117
    public function addHeader(string $name, string $value): Curl
118
    {
119
        $this->_headers[$name] = $value;
120
        return $this;
121
    }
122
123
    /**
124
     * @param string $name
125
     * @return Curl
126
     */
127
    public function removeHeader(string $name): Curl
128
    {
129
        if (array_key_exists($name, $this->_headers)) unset($this->_headers[$name]);
130
        return $this;
131
    }
132
133
    /**
134
     * @return array
135
     */
136
    public function getHeaders(): array
137
    {
138
        return $this->_headers;
139
    }
140
141
    public function setIsFileUpload(bool $is_file): Curl
142
    {
143
        $this->addHeader('Content-Type', 'multipart/form-data');
144
        $this->_isfile = $is_file;
145
        return $this;
146
    }
147
148
    public function isFileUpload(): bool
149
    {
150
        return $this->_isfile;
151
    }
152
153
    /**
154
     * @param bool $switch
155
     * @return $this
156
     */
157
    public function cacheRequests(bool $switch): Curl
158
    {
159
        $this->_enable_cache = $switch;
160
        return $this;
161
    }
162
163
    /**
164
     * @return CurlResponse
165
     */
166
    public function curl(): CurlResponse
167
    {
168
        $post_data      = null;
169
        $send_headers   = [];
170
        $request_uri    = $this->getURL();
171
172
        //  Headers
173
        foreach ($this->getHeaders() as $k => $v) $send_headers[] = "{$k}: {$v}";
174
175
        // Request Data
176
        switch ($this->getMethod()) {
177
            case 'GET':
178
            case 'DELETE':
179
                foreach ($this->getDataParams() as $name => $value) $request_uri = $this->setGetVar($name, $value, $request_uri);
180
                break;
181
182
            case 'POST':
183
                $post_data = ($this->isFileUpload()) ? $this->getDataParams() : http_build_query($this->getDataParams());
184
                break;
185
            case 'PUT':
186
                $post_data = http_build_query($this->getDataParams());
187
                break;
188
        }
189
190
        if ($this->_enable_cache && $this->getMethod() === 'GET') {
191
            $cache_key = urlencode($request_uri) . implode(',', $send_headers);
192
            if (array_key_exists($cache_key, self::$_cache)) {
193
                return self::$_cache[$cache_key];
194
            }
195
        }
196
197
        $ch = curl_init();
198
        curl_setopt($ch, CURLOPT_URL, $request_uri);
199
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
200
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->getMethod());
201
        curl_setopt($ch, CURLOPT_USERAGENT, self::USER_AGENT);
202
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
203
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);
204
        curl_setopt($ch, CURLOPT_ENCODING, '');
205
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
206
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
207
        curl_setopt($ch, CURLINFO_HEADER_OUT, true);
208
        curl_setopt($ch, CURLOPT_AUTOREFERER, true);
209
210
        if (!empty($send_headers))  curl_setopt($ch, CURLOPT_HTTPHEADER, $send_headers);
211
        if (!is_null($post_data))   {
212
            curl_setopt($ch, CURLOPT_POST, 1);
213
            curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
214
        }
215
216
        $result     = curl_exec($ch);
217
        $http_code  = curl_getinfo($ch, CURLINFO_HTTP_CODE);
218
        
219
        $response = new CurlResponse($http_code, $result);
220
        if ($this->_enable_cache && isset($cache_key)) self::$_cache[$cache_key] = $response;
221
222
        return $response;
223
    }
224
225
    /**
226
     * @see Curl::curl()
227
     *
228
     * @return CurlResponse
229
     */
230
    public function curl_json()
231
    {
232
        $this->addHeader('Accept', 'application/json');
233
        return $this->curl();
234
    }
235
236
    /**
237
     * @param string $key
238
     * @param string $value
239
     * @param string $url
240
     * @return string
241
     * @throws InvalidArgumentException If URL is malformed
242
     */
243
    private function setGetVar(string $key, string $value, string $url): string
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

243
    private function setGetVar(string $key, /** @scrutinizer ignore-unused */ string $value, string $url): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $key is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

243
    private function setGetVar(/** @scrutinizer ignore-unused */ string $key, string $value, string $url): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
244
    {
245
        if (!self::is_absolute_url($url)) {
246
            $isRelative = true;
0 ignored issues
show
Unused Code introduced by
The assignment to $isRelative is dead and can be removed.
Loading history...
247
            $uri = 'http://dummy.com/' . ltrim($url, '/'); 
0 ignored issues
show
Unused Code introduced by
The assignment to $uri is dead and can be removed.
Loading history...
248
            throw new InvalidArgumentException('Curl::setGetVar() requires $url to be an absolute url');
249
        } else {
250
            $isRelative = false;
251
            $uri = $url;
252
        }
253
254
        // try to parse uri
255
        $parts = parse_url($uri);
256
        if (!$parts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
257
            throw new InvalidArgumentException("Can't parse URL: " . $uri);
258
        }
259
260
        // Parse params and add new variable
261
        $params = [];
262
        if (isset($parts['query'])) {
263
            parse_str($parts['query'], $params);
264
        }
265
        $params[$varname] = $varvalue;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $varname seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $varvalue seems to be never defined.
Loading history...
266
267
        // Generate URI segments and formatting
268
        $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
269
        $user = (isset($parts['user']) && $parts['user'] != '') ? $parts['user'] : '';
270
271
        if ($user != '') {
272
            // format in either user:[email protected] or [email protected]
273
            $user .= (isset($parts['pass']) && $parts['pass'] != '') ? ':' . $parts['pass'] . '@' : '@';
274
        }
275
276
        $host = (isset($parts['host'])) ? $parts['host'] : '';
277
        $port = (isset($parts['port']) && $parts['port'] != '') ? ':' . $parts['port'] : '';
278
        $path = (isset($parts['path']) && $parts['path'] != '') ? $parts['path'] : '';
279
280
        // handle URL params which are existing / new
281
        $params = ($params) ? '?' . http_build_query($params, null, $separator) : '';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $separator seems to be never defined.
Loading history...
Bug introduced by
null of type null is incompatible with the type string expected by parameter $numeric_prefix of http_build_query(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

281
        $params = ($params) ? '?' . http_build_query($params, /** @scrutinizer ignore-type */ null, $separator) : '';
Loading history...
282
283
        // keep fragments (anchors) intact.
284
        $fragment = (isset($parts['fragment']) && $parts['fragment'] != '') ? '#' . $parts['fragment'] : '';
285
286
        // Recompile URI segments
287
        $newUri = $scheme . '://' . $user . $host . $port . $path . $params . $fragment;
288
289
        if ($isRelative) {
0 ignored issues
show
introduced by
The condition $isRelative is always false.
Loading history...
290
            return str_replace('http://dummy.com/', '', $newUri);
291
        }
292
293
        return $newUri;
294
    }
295
296
    /**
297
     * @param string $url
298
     * @return bool
299
     */
300
    public static function is_absolute_url(string $url): bool
301
    {
302
        // Strip off the query and fragment parts of the URL before checking
303
        if (($queryPosition = strpos($url, '?')) !== false) {
304
            $url = substr($url, 0, $queryPosition - 1);
305
        }
306
        if (($hashPosition = strpos($url, '#')) !== false) {
307
            $url = substr($url, 0, $hashPosition - 1);
308
        }
309
        $colonPosition = strpos($url, ':');
310
        $slashPosition = strpos($url, '/');
311
        return (
312
            // Base check for existence of a host on a compliant URL
313
            parse_url($url, PHP_URL_HOST)
314
            // Check for more than one leading slash without a protocol.
315
            // While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers,
316
            // and hence a potential security risk. Single leading slashes are not an issue though.
317
            || preg_match('%^\s*/{2,}%', $url)
318
            || (
319
                // If a colon is found, check if it's part of a valid scheme definition
320
                // (meaning its not preceded by a slash).
321
                $colonPosition !== false
322
                && ($slashPosition === false || $colonPosition < $slashPosition)
323
            )
324
        );
325
    }
326
}
327