Completed
Pull Request — master (#5)
by Haruaki
01:47
created

CurlSoapClient   F

Complexity

Total Complexity 69

Size/Duplication

Total Lines 309
Duplicated Lines 5.18 %

Coupling/Cohesion

Components 0
Dependencies 0

Test Coverage

Coverage 86.98%

Importance

Changes 0
Metric Value
wmc 69
lcom 0
cbo 0
dl 16
loc 309
ccs 167
cts 192
cp 0.8698
rs 2.8301
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 3
A __destruct() 0 6 2
A ___curlSetOpt() 0 4 1
A __getCookies() 0 4 1
A __setCookie() 0 8 2
A ___configHeader() 0 16 4
A ___configCompression() 0 12 4
A ___configTimeout() 0 9 3
C __doRequest() 0 47 12
A ___configHttpAuthentication() 8 11 4
B ___configProxy() 8 17 7
D ___curlCall() 0 86 23
A ___isNotEmptyExtProperty() 0 4 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CurlSoapClient 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 CurlSoapClient, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * curlsoapclient - SoapClient with ext-curl. -
4
 *
5
 * @author    aaharu
6
 * @copyright Copyright (c) 2014 aaharu
7
 * @license   MIT License
8
 */
9
10
namespace Aaharu\Soap;
11
12
use SoapClient;
13
14
/**
15
 * @see https://github.com/php/php-src/tree/master/ext/soap
16
 */
17
class CurlSoapClient extends SoapClient
18
{
19
    protected $curl = null; ///< cURL handle
20
    protected $redirect_max; ///< max redirect counts
21
    protected $curl_timeout; ///< cURL request time-out seconds
22
    private $redirect_count = 0;
23
24 12
    public function __construct($wsdl, array $options)
25
    {
26 12
        parent::__construct($wsdl, $options);
27 12
        $this->redirect_max = 5;
28 12
        if (isset($options['redirect_max'])) {
29 1
            $this->redirect_max = (int)$options['redirect_max'];
30 1
        }
31 12
        $this->curl_timeout = 30;
32 12
        if (isset($options['curl_timeout'])) {
33 1
            $this->curl_timeout = (int)$options['curl_timeout'];
34 1
        }
35 12
        $this->curl = curl_init();
36 12
        $this->_cookies = array();
37 12
    }
38
39 12
    public function __destruct()
40
    {
41 12
        if (isset($this->curl)) {
42 12
            curl_close($this->curl);
43 12
        }
44 12
    }
45
46 1
    public function ___curlSetOpt($option, $value)
47
    {
48 1
        curl_setopt($this->curl, $option, $value);
49 1
    }
50
51 1
    public function __getCookies()
52
    {
53 1
        return $this->_cookies;
54
    }
55
56 1
    public function __setCookie($name, $value = null)
57
    {
58 1
        if (!isset($value)) {
59
            unset($this->_cookies[$name]);
60
            return;
61
        }
62 1
        $this->_cookies[$name] = (array)$value;
63 1
    }
64
65
    /**
66
     * Execute SOAP requests.
67
     *
68
     * @param string $request SOAP request
69
     * @param string $location SOAP address
70
     * @param string $action SOAP action
71
     * @param int $version SOAP version
72
     * @param int $one_way
73
     * @throws \Exception
74
     * @throws \SoapFault
75
     * @return string|object (string) SOAP response / (object) SoapFault object
76
     */
77 12
    public function __doRequest($request, $location, $action, $version, $one_way = 0)
78
    {
79 12
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
80 12
        curl_setopt($this->curl, CURLOPT_HEADER, true);
81 12
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $request);
82 12
        if (isset($this->trace) && $this->trace) {
83 3
            curl_setopt($this->curl, CURLINFO_HEADER_OUT, true);
84 3
        }
85
86 12
        $this->___configHeader($action, $version);
87 12
        $this->___configCompression();
88 12
        $this->___configTimeout();
89 12
        if ($this->___isNotEmptyExtProperty('_user_agent')) {
90 1
            curl_setopt($this->curl, CURLOPT_USERAGENT, $this->_user_agent);
91 1
        }
92 12
        $this->___configHttpAuthentication();
93 12
        $this->___configProxy();
94 12
        if (isset($this->_ssl_method) && is_int($this->_ssl_method)) {
95
            switch ($this->_ssl_method) {
96
                case SOAP_SSL_METHOD_SSLv2:
97
                    curl_setopt($this->curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv2);
98
                    break;
99
                case SOAP_SSL_METHOD_SSLv3:
100
                    curl_setopt($this->curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3);
101
                    break;
102
                default:
103
                    curl_setopt($this->curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_DEFAULT);
104
                    break;
105
            }
106
        }
107
108
        try {
109 12
            $response = $this->___curlCall($location);
110 12
        } catch (\SoapFault $fault) {
111 7
            if (isset($this->_exceptions) && empty($this->_exceptions)) {
112
                // if exceptions option is false, return \SoapFault object
113 1
                return $fault;
114
            }
115 6
            throw $fault;
116
        }
117
118 5
        if ($one_way) {
119
            return '';
120
        }
121
122 5
        return $response;
123
    }
124
125
    /**
126
     * set CURLOPT_HTTPHEADER.
127
     *
128
     * @param string $action SOAP action
129
     * @param int $version SOAP version
130
     * @return void
131
     */
132 12
    private function ___configHeader($action, $version)
133
    {
134 12
        $header = array();
135 12
        if (isset($this->_keep_alive) && empty($this->_keep_alive)) {
136 1
            $header[] = 'Connection: close';
137 1
        } else {
138 11
            $header[] = 'Connection: Keep-Alive';
139
        }
140 12
        if ($version === SOAP_1_2) {
141 1
            $header[] = "Content-Type: application/soap+xml; charset=utf-8; action=\"{$action}\"";
142 1
        } else {
143 11
            $header[] = 'Content-Type: text/xml; charset=utf-8';
144 11
            $header[] = "SOAPAction: \"{$action}\"";
145
        }
146 12
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, $header);
147 12
    }
148
149
    /**
150
     * set CURLOPT_ENCODING.
151
     *
152
     * @return void
153
     */
154 12
    private function ___configCompression()
155
    {
156 12
        if (isset($this->compression)) {
157 3
            if ($this->compression & SOAP_COMPRESSION_ACCEPT) {
158 1
                curl_setopt($this->curl, CURLOPT_ENCODING, '');
159 3
            } elseif ($this->compression & SOAP_COMPRESSION_DEFLATE) {
160 1
                curl_setopt($this->curl, CURLOPT_ENCODING, 'deflate');
161 1
            } else {
162 1
                curl_setopt($this->curl, CURLOPT_ENCODING, 'gzip');
163
            }
164 3
        }
165 12
    }
166
167
    /**
168
     * set CURLOPT_CONNECTTIMEOUT and CURLOPT_TIMEOUT.
169
     *
170
     * @return void
171
     */
172 12
    private function ___configTimeout()
173
    {
174 12
        $connection_timeout = 10; // default
175 12
        if (isset($this->_connection_timeout) && is_int($this->_connection_timeout)) {
176 1
            $connection_timeout = $this->_connection_timeout;
177 1
        }
178 12
        curl_setopt($this->curl, CURLOPT_CONNECTTIMEOUT, $connection_timeout);
179 12
        curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->curl_timeout);
180 12
    }
181
182
    /**
183
     * set CURLOPT_HTTPAUTH.
184
     *
185
     * @return void
186
     */
187 12
    private function ___configHttpAuthentication()
188
    {
189 12 View Code Duplication
        if ($this->___isNotEmptyExtProperty('_login') && $this->___isNotEmptyExtProperty('_password')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
190 12
            curl_setopt($this->curl, CURLOPT_USERPWD, $this->_login . ':' . $this->_password);
191 2
            if (property_exists($this, '_digest')) {
192 2
                curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE);
193 1
            } else {
194 1
                curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
195 1
            }
196
        }
197 2
    }
198 12
199
    /**
200
     * set proxy options.
201
     *
202
     * @return void
203
     */
204
    private function ___configProxy()
205 12
    {
206
        if ($this->___isNotEmptyExtProperty('_proxy_host')) {
207 12
            curl_setopt($this->curl, CURLOPT_PROXY, $this->_proxy_host);
208
        }
209
        if (isset($this->_proxy_port) && is_int($this->_proxy_port)) {
210 12
            curl_setopt($this->curl, CURLOPT_PROXYPORT, $this->_proxy_port);
211
        }
212 View Code Duplication
        if ($this->___isNotEmptyExtProperty('_proxy_login') && $this->___isNotEmptyExtProperty('_proxy_password')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
213 12
            curl_setopt($this->curl, CURLOPT_PROXYUSERPWD, $this->_proxy_login . ':' . $this->_proxy_password);
214 12
            if (property_exists($this, '_digest')) {
215
                curl_setopt($this->curl, CURLOPT_PROXYAUTH, CURLAUTH_ANYSAFE);
216
            } else {
217
                curl_setopt($this->curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
218
            }
219
        }
220
    }
221
222 12
    /**
223
     * Request cURL.
224
     *
225
     * @param[in] string $location SOAP address
226
     * @param string $location
227
     * @throws \SoapFault
228
     * @return mixed response body
229
     */
230
    private function ___curlCall($location)
231 12
    {
232
        curl_setopt($this->curl, CURLOPT_URL, $location);
233 12
234
        if (!empty($this->_cookies)) {
235 12
            $cookies = array();
236 1
            foreach ($this->_cookies as $cookie_name => $cookie_value) {
237 1
                $cookies[] = $cookie_name . '=' . $cookie_value[0];
238 1
            }
239 1
            curl_setopt($this->curl, CURLOPT_COOKIE, implode('; ', $cookies));
240 1
        }
241 1
242
        $response = curl_exec($this->curl);
243 12
        if ($response === false) {
244 12
            throw new \SoapFault(
245 2
                'HTTP',
246 2
                'Error Fetching http, ' . curl_error($this->curl) . ' (' . curl_errno($this->curl) . ')'
247 2
            );
248 2
        }
249
250
        $header_size = curl_getinfo($this->curl, CURLINFO_HEADER_SIZE);
251 10
        $response_header = substr($response, 0, $header_size);
252 10
        $response_body = substr($response, $header_size);
253 10
        $http_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
254 10
255
        if (isset($this->trace) && $this->trace) {
256 10
            $this->__last_request_headers = curl_getinfo($this->curl, CURLINFO_HEADER_OUT);
257 3
            $this->__last_response_headers = $response_header;
258 3
        }
259 3
260
        if ($http_code >= 300 && $http_code < 400) {
261 10
            $tmp = stristr($response_header, 'Location:');
262 4
            $line_end = strpos($tmp, "\n"); // "\r" will be trimmed
263 4
            if ($line_end === false) {
264 4
                throw new \SoapFault('HTTP', 'Error Redirecting, No Location');
265 1
            }
266
            $new_location = trim(substr($tmp, 9, $line_end - 9));
267 3
            $url = parse_url($new_location);
268 3
            if ($url === false ||
269 3
                empty($url['scheme']) ||
270 3
                preg_match('/^https?$/i', $url['scheme']) !== 1
271 2
            ) {
272 3
                throw new \SoapFault('HTTP', 'Error Redirecting, Invalid Location');
273 1
            }
274
            if (++$this->redirect_count > $this->redirect_max) {
275 2
                throw new \SoapFault('HTTP', 'Redirection limit reached, aborting');
276 1
            }
277
            return $this->___curlCall($new_location);
278 2
        }
279
280
        if ($http_code >= 400) {
281 7
            $is_error = false;
282 3
            $response_length = strlen($response_body);
283 3
            if ($response_length === 0) {
284 3
                $is_error = true;
285 1
            } elseif ($response_length > 0) {
286 3
                $is_xml = false;
287 2
                $content_type = curl_getinfo($this->curl, CURLINFO_CONTENT_TYPE);
288 2
                if ($content_type !== null) {
289 2
                    $separator_position = strpos($content_type, ';');
290 2
                    if ($separator_position !== false) {
291 2
                        $content_type = substr($content_type, 0, $separator_position);
292 2
                    }
293 2
                    if ($content_type === 'text/xml' || $content_type === 'application/soap+xml') {
294 2
                        $is_xml = true;
295 1
                    }
296 1
                }
297 2
                if (!$is_xml) {
298 2
                    $str = ltrim($response_body);
299 1
                    if (strncmp($str, '<?xml', 5)) {
300 1
                        $is_error = true;
301 1
                    }
302 1
                }
303 1
            }
304 2
305
            if ($is_error) {
306 3
                $string_http_code = (string)$http_code;
307 2
                $code_position = strpos($response_header, $string_http_code);
308 2
                $tmp = substr($response_header, $code_position + strlen($string_http_code));
309 2
                $http_message = trim(strstr($tmp, "\n", true));
310 2
                throw new \SoapFault('HTTP', $http_message);
311 2
            }
312
        }
313 1
314
        return $response_body;
315 5
    }
316
317
318
    /**
319
     * @param string $property
320
     */
321
    private function ___isNotEmptyExtProperty($property)
322
    {
323
        return isset($this->{$property}) && is_string($this->{$property}) && strlen($this->{$property}) > 0;
324
    }
325
}
326