OptionFactory   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 350
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 21
c 2
b 0
f 0
lcom 1
cbo 4
dl 0
loc 350
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
C build() 0 38 8
A getOptionKey() 0 15 4
B configureCallback() 0 28 5
A getCurlConstants() 0 14 4
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\Option;
12
13
use Jgut\Spiral\Exception\OptionException;
14
15
/**
16
 * cURL option wrappers factory.
17
 */
18
abstract class OptionFactory
19
{
20
    /**
21
     * Accepted cURL option aliases.
22
     *
23
     * @var array
24
     */
25
    protected static $aliasMap = [
26
        'http-header'        => CURLOPT_HTTPHEADER,
27
28
        'crlf'               => CURLOPT_CRLF,
29
        'header-out'         => CURLINFO_HEADER_OUT,
30
        'return-transfer'    => CURLOPT_RETURNTRANSFER,
31
        'verbose'            => CURLOPT_VERBOSE,
32
        'user-agent'         => CURLOPT_USERAGENT,
33
        'ssl-version'        => CURLOPT_SSLVERSION,
34
        'cookie-file'        => CURLOPT_COOKIEFILE,
35
        'cookie-jar'         => CURLOPT_COOKIEJAR,
36
        'referer'            => CURLOPT_REFERER,
37
        'auto-referer'       => CURLOPT_AUTOREFERER,
38
        'file-time'          => CURLOPT_FILETIME,
39
        'user-password'      => CURLOPT_USERPWD,
40
        'http-version'       => CURLOPT_HTTP_VERSION,
41
        'port'               => CURLOPT_PORT,
42
        'encoding'           => CURLOPT_ENCODING,
43
        'buffer-size'        => CURLOPT_BUFFERSIZE,
44
        'post-redir'         => CURLOPT_POSTREDIR,
45
        'stderr'             => CURLOPT_STDERR,
46
        'netrc'              => CURLOPT_NETRC,
47
48
        'header'             => CURLOPT_HEADER,
49
        'include'            => CURLOPT_HEADER,
50
51
        'connect-timeout'    => CURLOPT_CONNECTTIMEOUT,
52
        'connection-timeout' => CURLOPT_CONNECTTIMEOUT,
53
54
        'timeout'            => CURLOPT_TIMEOUT,
55
        'max-time'           => CURLOPT_TIMEOUT,
56
57
        'ssl_verify_host'    => CURLOPT_SSL_VERIFYHOST,
58
        'ssl_verify_peer'    => CURLOPT_SSL_VERIFYPEER,
59
        'insecure'           => CURLOPT_SSL_VERIFYPEER,
60
61
        'follow-location'    => CURLOPT_FOLLOWLOCATION,
62
        'follow-redirects'   => CURLOPT_FOLLOWLOCATION,
63
        'location'           => CURLOPT_FOLLOWLOCATION,
64
65
        'max-redirs'         => CURLOPT_MAXREDIRS,
66
        'max-redirects'      => CURLOPT_MAXREDIRS,
67
68
        'cookie'             => CURLOPT_COOKIE,
69
        'cookies'            => CURLOPT_COOKIE,
70
71
        'http-auth'          => CURLOPT_HTTPAUTH,
72
        'auth'               => CURLOPT_HTTPAUTH,
73
74
        'unrestricted-auth'  => CURLOPT_UNRESTRICTED_AUTH,
75
        'location-trusted'   => CURLOPT_UNRESTRICTED_AUTH,
76
77
        'forbid-reuse'       => CURLOPT_FORBID_REUSE,
78
        'fresh-connect'      => CURLOPT_FRESH_CONNECT,
79
80
        'max-connections'    => CURLOPT_MAXCONNECTS,
81
    ];
82
83
    /**
84
     * cURL accepted options and class mapper.
85
     *
86
     * @var array
87
     */
88
    protected static $typeMap = [
89
        // Boolean
90
        // Perform proxy authentication and connection setup but no data transfer
91
        CURLOPT_CONNECT_ONLY      => ['type' => 'bool'],
92
        // Convert Unix newlines to CRLF newlines on transfers
93
        CURLOPT_CRLF              => ['type' => 'bool'],
94
        // Include request header in the output
95
        CURLINFO_HEADER_OUT       => ['type' => 'bool'],
96
        // Include response header in the output
97
        CURLOPT_HEADER            => ['type' => 'bool'],
98
        // Return the transfer as a string instead of outputting
99
        CURLOPT_RETURNTRANSFER    => ['type' => 'bool'],
100
        // Output verbose information
101
        CURLOPT_VERBOSE           => ['type' => 'bool'],
102
        // False to stop cURL from verifying the peer's certificate
103
        CURLOPT_SSL_VERIFYPEER    => ['type' => 'bool'],
104
        // Follow any Location headers sent by server
105
        CURLOPT_FOLLOWLOCATION    => ['type' => 'bool'],
106
        // Automatically set the http referer header when following a redirect
107
        CURLOPT_AUTOREFERER       => ['type' => 'bool'],
108
        // Keep sending the username and password when following locations
109
        CURLOPT_UNRESTRICTED_AUTH => ['type' => 'bool'],
110
        // Get HTTP header for modification date of file
111
        CURLOPT_FILETIME          => ['type' => 'bool'],
112
        // Automatically close connection after processing
113
        CURLOPT_FORBID_REUSE      => ['type' => 'bool'],
114
        // Force to use new connection instead of cached
115
        CURLOPT_FRESH_CONNECT     => ['type' => 'bool'],
116
        // Scan ~/.netrc file for user credentials
117
        CURLOPT_NETRC             => ['type' => 'bool'],
118
        // Tunnel through a given HTTP proxy
119
        CURLOPT_HTTPPROXYTUNNEL   => ['type' => 'bool'],
120
121
        // Integer
122
        // Number of seconds to wait while trying to connect. 0 to wait indefinitely
123
        CURLOPT_CONNECTTIMEOUT => ['type' => 'int'],
124
        // Maximum number of seconds to allow cURL functions to execute
125
        CURLOPT_TIMEOUT        => ['type' => 'int'],
126
        // The maximum amount of HTTP redirections to follow
127
        CURLOPT_MAXREDIRS      => ['type' => 'int'],
128
        // Alternative port number to connect to
129
        CURLOPT_PORT           => ['type' => 'int', 'max' => 99999],
130
        // Port number of the proxy to connect to
131
        CURLOPT_PROXYPORT      => ['type' => 'int'],
132
        // Size of the buffer for each read
133
        CURLOPT_BUFFERSIZE     => ['type' => 'int'],
134
        // The maximum amount of persistent connections
135
        CURLOPT_MAXCONNECTS    => ['type' => 'int'],
136
137
        // String
138
        // Contents of the "User-Agent: " header to be used in a HTTP request
139
        CURLOPT_USERAGENT => ['type' => 'string'],
140
        // Contents of the "Referer: " header to be used in a HTTP request
141
        CURLOPT_REFERER   => ['type' => 'string'],
142
        // Contents of the "Accept-Encoding: " header. This enables decoding of the response
143
        CURLOPT_ENCODING  => ['type' => 'string'],
144
        // Alternative location to output errors
145
        CURLOPT_STDERR    => ['type' => 'string'],
146
        // HTTP proxy to tunnel requests through
147
        CURLOPT_PROXY     => ['type' => 'string'],
148
149
        // File
150
        // Name of the file containing the cookie data
151
        CURLOPT_COOKIEFILE => ['type' => 'file'],
152
        // Name of a file to save all internal cookies to when the handle is closed
153
        CURLOPT_COOKIEJAR  => ['type' => 'file'],
154
155
        // Regex
156
        // Which SSL/TLS version to use
157
        CURLOPT_SSLVERSION        => [
158
            'type' => 'regex',
159
            'regex' => '/^[0-6]$/',
160
            'message' => '"%s" is not valid SSL version',
161
        ],
162
        // Verify existence of a common name in peer certificate, and matches hostname
163
        CURLOPT_SSL_VERIFYHOST => [
164
            'type' => 'regex',
165
            'regex' => '/^1|2$/',
166
            'message' => '"%s" is not valid SSL verify host value',
167
        ],
168
        // Bit mask to maintain redirection type
169
        CURLOPT_POSTREDIR => [
170
            'type' => 'regex',
171
            'regex' => '/^1|2|4$/',
172
            'message' => '"%s" is not valid POST redirection value',
173
        ],
174
        /*
175
         * Proxy type
176
         * 0: CURLPROXY_HTTP             4: CURLPROXY_SOCKS4,
177
         * 5: CURLPROXY_SOCKS5           6: CURLPROXY_SOCKS4A
178
         * 7: CURLPROXY_SOCKS5_HOSTNAME
179
         */
180
        CURLOPT_PROXYTYPE => [
181
            'type' => 'regex',
182
            'regex' => '/^0|4|5|6|7$/',
183
            'message' => '"%s" is not a valid CURLOPT_PROXYTYPE value',
184
        ],
185
        // Username and password formatted as "username:password" to use for the connection
186
        CURLOPT_USERPWD => [
187
            'type' => 'regex',
188
            'regex' => '/^[^\n:]+:[^\n:]+$/',
189
            'message' => '"%s" is not a valid CURLOPT_USERPWD value',
190
        ],
191
        // Username and password formatted as "username:password" to use for proxy
192
        CURLOPT_PROXYUSERPWD => [
193
            'type' => 'regex',
194
            'regex' => '/^[^\n:]+:[^\n:]+$/',
195
            'message' => '"%s" is not a valid CURLOPT_PROXYUSERPWD value',
196
        ],
197
        /*
198
         * HTTP authentication method(s)
199
         *
200
         *   1: CURLAUTH_BASIC           2: CURLAUTH_DIGEST
201
         *   4: CURLAUTH_GSSNEGOTIATE    8: CURLAUTH_NTLM
202
         * -17: CURLAUTH_ANY           -18: CURLAUTH_ANYSAFE
203
         */
204
        CURLOPT_HTTPAUTH => [
205
            'type' => 'regex',
206
            'regex' => '/^1|2|4|8|-17|-18$/',
207
            'message' => '"%s" is not a valid CURLOPT_HTTPAUTH value',
208
        ],
209
        /*
210
         * HTTP authentication method(s) for the proxy
211
         *
212
         *   1: CURLAUTH_BASIC           2: CURLAUTH_DIGEST
213
         *   4: CURLAUTH_GSSNEGOTIATE    8: CURLAUTH_NTLM
214
         * -17: CURLAUTH_ANY           -18: CURLAUTH_ANYSAFE
215
         */
216
        CURLOPT_PROXYAUTH => [
217
            'type' => 'regex',
218
            'regex' => '/^1|2|4|8|-17|-18$/',
219
            'message' => '"%s" is not a valid CURLOPT_PROXYAUTH value',
220
        ],
221
222
        // Which HTTP version to use. "1.0" for CURL_HTTP_VERSION_1_0 or "1.1" for CURL_HTTP_VERSION_1_1
223
        CURLOPT_HTTP_VERSION => ['type' => 'callback'],
224
        // Contents of the "Cookie: " header to be used in the HTTP request. Can be an array
225
        CURLOPT_COOKIE       => ['type' => 'callback'],
226
    ];
227
228
    /**
229
     * Defined cURL constants.
230
     *
231
     * @var array
232
     */
233
    protected static $curlConstants;
234
235
    /**
236
     * Build cURL option.
237
     *
238
     * @param int|string $option
239
     * @param mixed      $value
240
     *
241
     * @throws OptionException
242
     *
243
     * @return OptionInterface
244
     */
245
    public static function build($option, $value)
246
    {
247
        $optionDefinition = ['type' => ''];
248
249
        $option = static::getOptionKey($option);
250
        if (array_key_exists($option, static::$typeMap)) {
251
            $optionDefinition = static::$typeMap[$option];
252
        }
253
254
        $optionClassName = preg_replace('/Factory$/', '', self::class) . ucfirst($optionDefinition['type']);
255
        $optionClass = new $optionClassName($option);
256
257
        switch (strtolower($optionDefinition['type'])) {
258
            case 'regex':
259
                /* @var OptionRegex $optionClass */
260
                $optionClass->setRegex($optionDefinition['regex']);
261
                if (array_key_exists('message', $optionDefinition)) {
262
                    $optionClass->setMessage($optionDefinition['message']);
263
                }
264
                break;
265
266
            case 'int':
267
                /* @var OptionInt $optionClass */
268
                $optionClass->setMin(array_key_exists('min', $optionDefinition) ? $optionDefinition['min'] : 0);
269
                if (array_key_exists('max', $optionDefinition)) {
270
                    $optionClass->setMax($optionDefinition['max']);
271
                }
272
                break;
273
274
            case 'callback':
275
                $optionClass = static::configureCallback($optionClass, $option);
276
                break;
277
        }
278
279
        $optionClass->setValue($value);
280
281
        return $optionClass;
282
    }
283
284
    /**
285
     * Get mapped option.
286
     *
287
     * @param int|string $option
288
     *
289
     * @throws OptionException
290
     *
291
     * @return int
292
     */
293
    public static function getOptionKey($option)
294
    {
295
        if (is_string($option)) {
296
            $option = strtolower(preg_replace('/[ _]+/', '-', trim($option)));
297
            if (array_key_exists($option, static::$aliasMap)) {
298
                $option = static::$aliasMap[strtolower($option)];
299
            }
300
        }
301
302
        if (!in_array($option, static::getCurlConstants(), true)) {
303
            throw new OptionException(sprintf('"%s" is not valid cURL option', $option));
304
        }
305
306
        return $option;
307
    }
308
309
    /**
310
     * Configure option callback.
311
     *
312
     * @param OptionCallback $optionClass
313
     * @param int            $option
314
     *
315
     * @throws OptionException
316
     *
317
     * @return OptionCallback
318
     */
319
    protected static function configureCallback(OptionCallback $optionClass, $option)
320
    {
321
        switch ($option) {
322
            case CURLOPT_HTTP_VERSION:
323
                $optionClass->setCallback(function ($value) {
324
                    $value = number_format((float) $value, 1, '.', '');
325
326
                    if (!preg_match('/^1.([01])$/', $value)) {
327
                        throw new OptionException(sprintf('"%s" is not a valid HTTP version', $value));
328
                    }
329
330
                    return constant('CURL_HTTP_VERSION_' . str_replace('.', '_', $value));
331
                });
332
                break;
333
334
            case CURLOPT_COOKIE:
335
                $optionClass->setCallback(function ($value) {
336
                    if (is_array($value)) {
337
                        $value = http_build_query($value, '', '; ');
338
                    }
339
340
                    return $value;
341
                });
342
                break;
343
        }
344
345
        return $optionClass;
346
    }
347
348
    /**
349
     * Get defined cURL constants.
350
     *
351
     * @return array
352
     */
353
    protected static function getCurlConstants()
354
    {
355
        if (static::$curlConstants === null) {
356
            static::$curlConstants = [];
357
358
            foreach (get_defined_constants(true)['curl'] as $key => $val) {
359
                if (preg_match('/(^CURLOPT_)|(^CURLINFO_HEADER_OUT$)/', $key)) {
360
                    static::$curlConstants[] = $val;
361
                }
362
            }
363
        }
364
365
        return static::$curlConstants;
366
    }
367
}
368