Curl   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 262
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
wmc 30
lcom 2
cbo 3
dl 0
loc 262
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A createFromDefaults() 0 4 1
B request() 0 31 5
A resetHandler() 0 19 4
A execute() 0 4 2
A setMethod() 0 20 4
A forgeOptions() 0 10 4
A forgeHeaders() 0 10 3
A responseInfo() 0 12 3
A __destruct() 0 4 1
A close() 0 8 2
1
<?php
2
3
/*
4
 * A PSR7 aware cURL client (https://github.com/juliangut/spiral).
5
 *
6
 * @license BSD-3-Clause
7
 * @link https://github.com/juliangut/spiral
8
 * @author Julián Gutiérrez <[email protected]>
9
 */
10
11
namespace Jgut\Spiral\Transport;
12
13
use Fig\Http\Message\RequestMethodInterface;
14
use Jgut\Spiral\Exception\TransportException;
15
use Jgut\Spiral\Option\OptionInterface;
16
17
/**
18
 * cURL transport handler.
19
 */
20
class Curl extends AbstractTransport
21
{
22
    /**
23
     * cURL error map.
24
     *
25
     * @var array
26
     */
27
    protected static $errorCategoryMap = [
28
        CURLE_FTP_WEIRD_SERVER_REPLY      => 'FTP',
29
        CURLE_FTP_ACCESS_DENIED           => 'FTP',
30
        CURLE_FTP_USER_PASSWORD_INCORRECT => 'FTP',
31
        CURLE_FTP_WEIRD_PASS_REPLY        => 'FTP',
32
        CURLE_FTP_WEIRD_USER_REPLY        => 'FTP',
33
        CURLE_FTP_WEIRD_PASV_REPLY        => 'FTP',
34
        CURLE_FTP_WEIRD_227_FORMAT        => 'FTP',
35
        CURLE_FTP_CANT_GET_HOST           => 'FTP',
36
        CURLE_FTP_CANT_RECONNECT          => 'FTP',
37
        CURLE_FTP_COULDNT_SET_BINARY      => 'FTP',
38
        CURLE_FTP_COULDNT_RETR_FILE       => 'FTP',
39
        CURLE_FTP_WRITE_ERROR             => 'FTP',
40
        CURLE_FTP_QUOTE_ERROR             => 'FTP',
41
        CURLE_FTP_COULDNT_STOR_FILE       => 'FTP',
42
        CURLE_FTP_COULDNT_SET_ASCII       => 'FTP',
43
        CURLE_FTP_PORT_FAILED             => 'FTP',
44
        CURLE_FTP_COULDNT_USE_REST        => 'FTP',
45
        CURLE_FTP_COULDNT_GET_SIZE        => 'FTP',
46
        CURLE_FTP_BAD_DOWNLOAD_RESUME     => 'FTP',
47
        CURLE_FTP_SSL_FAILED              => 'FTP',
48
        CURLE_SSL_CONNECT_ERROR           => 'SSL',
49
        CURLE_SSL_PEER_CERTIFICATE        => 'SSL',
50
        CURLE_SSL_ENGINE_NOTFOUND         => 'SSL',
51
        CURLE_SSL_ENGINE_SETFAILED        => 'SSL',
52
        CURLE_SSL_CERTPROBLEM             => 'SSL',
53
        CURLE_SSL_CIPHER                  => 'SSL',
54
        CURLE_SSL_CACERT                  => 'SSL',
55
        CURLE_LDAP_CANNOT_BIND            => 'LDAP',
56
        CURLE_LDAP_SEARCH_FAILED          => 'LDAP',
57
        CURLE_LDAP_INVALID_URL            => 'LDAP',
58
        CURLE_COULDNT_RESOLVE_PROXY       => 'proxy',
59
    ];
60
61
    /**
62
     * Default options.
63
     *
64
     * @var array
65
     */
66
    protected static $defaultOptions = [
67
        CURLOPT_VERBOSE           => false,
68
        CURLOPT_HTTP_VERSION      => 1.1,
69
        CURLOPT_USERAGENT         => self::class,
70
        CURLOPT_CONNECTTIMEOUT    => 60,
71
        CURLOPT_TIMEOUT           => 60,
72
        CURLOPT_CRLF              => false,
73
        CURLOPT_SSLVERSION        => 0,
74
        CURLOPT_SSL_VERIFYPEER    => true,
75
        CURLOPT_SSL_VERIFYHOST    => 2,
76
        CURLOPT_AUTOREFERER       => true,
77
        CURLOPT_FOLLOWLOCATION    => true,
78
        CURLOPT_MAXREDIRS         => 10,
79
        CURLOPT_UNRESTRICTED_AUTH => false,
80
        CURLOPT_RETURNTRANSFER    => true,
81
        CURLOPT_HEADER            => true,
82
        CURLOPT_FORBID_REUSE      => false,
83
        CURLOPT_FRESH_CONNECT     => false,
84
        CURLOPT_MAXCONNECTS       => 5,
85
    ];
86
87
    /**
88
     * cURL resource handler.
89
     *
90
     * @var resource
91
     */
92
    private $handler;
93
94
    /**
95
     * Create cURL transport manager.
96
     *
97
     * @param array $options
98
     *
99
     * @throws \Jgut\Spiral\Exception\OptionException
100
     */
101
    public function __construct(array $options = [])
102
    {
103
        $this->setOptions($options);
104
    }
105
106
    /**
107
     * Create cURL transport manager with default options.
108
     *
109
     * @return static
110
     */
111
    public static function createFromDefaults()
112
    {
113
        return new static(static::$defaultOptions);
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     *
119
     * @throws TransportException
120
     */
121
    public function request($method, $uri, array $headers = [], $requestBody = null)
122
    {
123
        $this->resetHandler();
124
125
        $method = strtoupper($method);
126
        $this->setMethod($method);
127
128
        $this->forgeOptions($this->options);
129
        $this->forgeHeaders($headers);
130
131
        if ($requestBody !== null && $requestBody) {
132
            curl_setopt($this->handler, CURLOPT_POSTFIELDS, $requestBody);
133
        }
134
        curl_setopt($this->handler, CURLOPT_URL, $uri);
135
136
        $response = $this->execute();
137
138
        if (curl_errno($this->handler) !== CURLE_OK) {
139
            $errCode = curl_errno($this->handler);
140
141
            $exception = new TransportException(
142
                curl_error($this->handler),
143
                $errCode,
144
                array_key_exists($errCode, static::$errorCategoryMap) ? static::$errorCategoryMap [$errCode] : ''
145
            );
146
147
            throw $exception;
148
        }
149
150
        return $response;
151
    }
152
153
    /**
154
     * Create or reuse existing handle.
155
     *
156
     * @return resource
157
     */
158
    protected function resetHandler()
159
    {
160
        if (is_resource($this->handler)) {
161
            if ($this->hasOption(CURLOPT_FORBID_REUSE, true)
162
                || $this->hasOption(CURLOPT_FRESH_CONNECT, true)
163
            ) {
164
                // on using CURLOPT_FRESH_CONNECT or CURLOPT_FORBID_REUSE
165
                // a curl_reset() is 20-30% slower than closing and reinit
166
                $this->close();
167
                $this->handler = curl_init();
168
            } else {
169
                curl_reset($this->handler);
170
            }
171
        } else {
172
            $this->handler = curl_init();
173
        }
174
175
        return $this->handler;
176
    }
177
178
    /**
179
     * Isolate curl execution.
180
     *
181
     * @return string
182
     */
183
    protected function execute()
184
    {
185
        return curl_exec($this->handler) ?: '';
186
    }
187
188
    /**
189
     * Set HTTP method on handler.
190
     *
191
     * @param string $method
192
     */
193
    protected function setMethod($method)
194
    {
195
        switch ($method) {
196
            case RequestMethodInterface::METHOD_HEAD:
197
                curl_setopt($this->handler, CURLOPT_NOBODY, true);
198
                break;
199
200
            case RequestMethodInterface::METHOD_GET:
201
                curl_setopt($this->handler, CURLOPT_HTTPGET, true);
202
                break;
203
204
            case RequestMethodInterface::METHOD_POST:
205
                curl_setopt($this->handler, CURLOPT_POST, true);
206
                curl_setopt($this->handler, CURLOPT_CUSTOMREQUEST, RequestMethodInterface::METHOD_POST);
207
                break;
208
209
            default:
210
                curl_setopt($this->handler, CURLOPT_CUSTOMREQUEST, $method);
211
        }
212
    }
213
214
    /**
215
     * Set cURL options on handler.
216
     *
217
     * @param OptionInterface[] $options
218
     */
219
    protected function forgeOptions(array $options)
220
    {
221
        foreach ($options as $option) {
222
            curl_setopt($this->handler, $option->getOption(), $option->getValue());
223
        }
224
225
        if ($this->hasOption(CURLOPT_USERPWD) && !$this->hasOption(CURLOPT_HTTPAUTH, CURLAUTH_BASIC)) {
226
            curl_setopt($this->handler, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
227
        }
228
    }
229
230
    /**
231
     * Set HTTP headers on handler.
232
     *
233
     * @param array $headers
234
     */
235
    protected function forgeHeaders(array $headers)
236
    {
237
        $headerList = [];
238
239
        foreach ($headers as $header => $value) {
240
            $headerList[] = sprintf('%s: %s', $header, is_array($value) ? implode(', ', $value) : (string) $value);
241
        }
242
243
        curl_setopt($this->handler, CURLOPT_HTTPHEADER, $headerList);
244
    }
245
246
    /**
247
     * {@inheritdoc}
248
     */
249
    public function responseInfo($option = null)
250
    {
251
        if (!is_resource($this->handler)) {
252
            return;
253
        }
254
255
        if ($option !== null) {
256
            return curl_getinfo($this->handler, $option);
257
        }
258
259
        return curl_getinfo($this->handler);
260
    }
261
262
    /**
263
     * Always free resources on destruction.
264
     */
265
    public function __destruct()
266
    {
267
        $this->close();
268
    }
269
270
    /**
271
     * {@inheritdoc}
272
     */
273
    public function close()
274
    {
275
        if (is_resource($this->handler)) {
276
            curl_close($this->handler);
277
278
            $this->handler = null;
279
        }
280
    }
281
}
282