Completed
Push — master ( c7f4e9...040a1a )
by Julián
02:28
created

OptionFactory::build()   C

Complexity

Conditions 8
Paths 12

Size

Total Lines 38
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 38
rs 5.3846
cc 8
eloc 23
nc 12
nop 2
1
<?php
2
/**
3
 * Spiral: PSR7 aware cURL client (https://github.com/juliangut/spiral)
4
 *
5
 * @link https://github.com/juliangut/spiral for the canonical source repository
6
 * @license https://raw.githubusercontent.com/juliangut/spiral/master/LICENSE
7
 */
8
9
namespace Jgut\Spiral\Option;
10
11
use Jgut\Spiral\Exception\OptionException;
12
use Jgut\Spiral\Option;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Jgut\Spiral\Option\Option.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
13
use Jgut\Spiral\Option\OptionCallback;
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
    private 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
81
    /**
82
     * cURL accepted options and class mapper.
83
     *
84
     * @var array
85
     */
86
    private static $typeMap = [
87
        // Boolean
88
        // Perform proxy authentication and connection setup but no data transfer
89
        CURLOPT_CONNECT_ONLY      => ['type' => 'bool'],
90
        // Convert Unix newlines to CRLF newlines on transfers
91
        CURLOPT_CRLF              => ['type' => 'bool'],
92
        // Include request header in the output
93
        CURLINFO_HEADER_OUT       => ['type' => 'bool'],
94
        // Include response header in the output
95
        CURLOPT_HEADER            => ['type' => 'bool'],
96
        // Return the transfer as a string instead of outputting
97
        CURLOPT_RETURNTRANSFER    => ['type' => 'bool'],
98
        // Output verbose information
99
        CURLOPT_VERBOSE           => ['type' => 'bool'],
100
        // False to stop cURL from verifying the peer's certificate
101
        CURLOPT_SSL_VERIFYPEER    => ['type' => 'bool'],
102
        // Follow any Location headers sent by server
103
        CURLOPT_FOLLOWLOCATION    => ['type' => 'bool'],
104
        // Automatically set the http referer header when following a redirect
105
        CURLOPT_AUTOREFERER       => ['type' => 'bool'],
106
        // Keep sending the username and password when following locations
107
        CURLOPT_UNRESTRICTED_AUTH => ['type' => 'bool'],
108
        // Get HTTP header for modification date of file
109
        CURLOPT_FILETIME          => ['type' => 'bool'],
110
        // Automatically close connection after processing
111
        CURLOPT_FORBID_REUSE      => ['type' => 'bool'],
112
        // Force to use new connection instead of cached
113
        CURLOPT_FRESH_CONNECT     => ['type' => 'bool'],
114
        // Scan ~/.netrc file for user credentials
115
        CURLOPT_NETRC             => ['type' => 'bool'],
116
        // Tunnel through a given HTTP proxy
117
        CURLOPT_HTTPPROXYTUNNEL   => ['type' => 'bool'],
118
119
        // Integer
120
        // Number of seconds to wait while trying to connect. 0 to wait indefinitely
121
        CURLOPT_CONNECTTIMEOUT => ['type' => 'int'],
122
        // Maximum number of seconds to allow cURL functions to execute
123
        CURLOPT_TIMEOUT        => ['type' => 'int'],
124
        // The maximum amount of HTTP redirections to follow
125
        CURLOPT_MAXREDIRS      => ['type' => 'int'],
126
        // Alternative port number to connect to
127
        CURLOPT_PORT           => ['type' => 'int', 'max' => 99999],
128
        // Port number of the proxy to connect to
129
        CURLOPT_PROXYPORT      => ['type' => 'int'],
130
        // Size of the buffer for each read
131
        CURLOPT_BUFFERSIZE     => ['type' => 'int'],
132
133
        // String
134
        // Contents of the "User-Agent: " header to be used in a HTTP request
135
        CURLOPT_USERAGENT => ['type' => 'string'],
136
        // Contents of the "Referer: " header to be used in a HTTP request
137
        CURLOPT_REFERER   => ['type' => 'string'],
138
        // Contents of the "Accept-Encoding: " header. This enables decoding of the response
139
        CURLOPT_ENCODING  => ['type' => 'string'],
140
        // Alternative location to output errors
141
        CURLOPT_STDERR    => ['type' => 'string'],
142
        // HTTP proxy to tunnel requests through
143
        CURLOPT_PROXY     => ['type' => 'string'],
144
145
        // File
146
        // Name of the file containing the cookie data
147
        CURLOPT_COOKIEFILE => ['type' => 'file'],
148
        // Name of a file to save all internal cookies to when the handle is closed
149
        CURLOPT_COOKIEJAR  => ['type' => 'file'],
150
151
        // Regex
152
        // Which SSL/TLS version to use
153
        CURLOPT_SSLVERSION        => [
154
            'type' => 'regex',
155
            'regex' => '/^[0-6]$/',
156
            'message' => '"%s" is not valid SSL version'
157
        ],
158
        // Verify existence of a common name in peer certificate, and matches hostname
159
        CURLOPT_SSL_VERIFYHOST => [
160
            'type' => 'regex',
161
            'regex' => '/^1|2$/',
162
            'message' => '"%s" is not valid SSL verify host value'
163
        ],
164
        // Bit mask to maintain redirection type
165
        CURLOPT_POSTREDIR => [
166
            'type' => 'regex',
167
            'regex' => '/^1|2|4$/',
168
            'message' => '"%s" is not valid POST redirection value'
169
        ],
170
        /*
171
         * Proxy type
172
         * 0: CURLPROXY_HTTP             4: CURLPROXY_SOCKS4,
173
         * 5: CURLPROXY_SOCKS5           6: CURLPROXY_SOCKS4A
174
         * 7: CURLPROXY_SOCKS5_HOSTNAME
175
         */
176
        CURLOPT_PROXYTYPE => [
177
            'type' => 'regex',
178
            'regex' => '/^0|4|5|6|7$/',
179
            'message' => '"%s" is not a valid CURLOPT_PROXYTYPE value'
180
        ],
181
        // Username and password formatted as "username:password" to use for the connection
182
        CURLOPT_USERPWD => [
183
            'type' => 'regex',
184
            'regex' => '/^[^\n:]+:[^\n:]+$/',
185
            'message' => '"%s" is not a valid CURLOPT_USERPWD value'
186
        ],
187
        // Username and password formatted as "username:password" to use for proxy
188
        CURLOPT_PROXYUSERPWD => [
189
            'type' => 'regex',
190
            'regex' => '/^[^\n:]+:[^\n:]+$/',
191
            'message' => '"%s" is not a valid CURLOPT_PROXYUSERPWD value'
192
        ],
193
        /*
194
         * HTTP authentication method(s)
195
         *
196
         *   1: CURLAUTH_BASIC           2: CURLAUTH_DIGEST
197
         *   4: CURLAUTH_GSSNEGOTIATE    8: CURLAUTH_NTLM
198
         * -17: CURLAUTH_ANY           -18: CURLAUTH_ANYSAFE
199
         */
200
        CURLOPT_HTTPAUTH => [
201
            'type' => 'regex',
202
            'regex' => '/^1|2|4|8|-17|-18$/',
203
            'message' => '"%s" is not a valid CURLOPT_HTTPAUTH value'
204
        ],
205
        /*
206
         * HTTP authentication method(s) for the proxy
207
         *
208
         *   1: CURLAUTH_BASIC           2: CURLAUTH_DIGEST
209
         *   4: CURLAUTH_GSSNEGOTIATE    8: CURLAUTH_NTLM
210
         * -17: CURLAUTH_ANY           -18: CURLAUTH_ANYSAFE
211
         */
212
        CURLOPT_PROXYAUTH => [
213
            'type' => 'regex',
214
            'regex' => '/^1|2|4|8|-17|-18$/',
215
            'message' => '"%s" is not a valid CURLOPT_PROXYAUTH value'
216
        ],
217
218
        // Which HTTP version to use. "1.0" for CURL_HTTP_VERSION_1_0 or "1.1" for CURL_HTTP_VERSION_1_1
219
        CURLOPT_HTTP_VERSION => ['type' => 'callback'],
220
        // Contents of the "Cookie: " header to be used in the HTTP request. Can be an array
221
        CURLOPT_COOKIE       => ['type' => 'callback'],
222
    ];
223
224
    /**
225
     * Build cURL option.
226
     *
227
     * @param int|string $option
228
     * @param mixed      $value
229
     *
230
     * @return \Jgut\Spiral\Option
231
     *
232
     * @throws \Jgut\Spiral\Exception\OptionException
233
     */
234
    public static function build($option, $value)
235
    {
236
        $optionDefinition = ['type' => ''];
237
238
        $option = static::getOptionKey($option);
239
        if (array_key_exists($option, static::$typeMap)) {
0 ignored issues
show
Bug introduced by
Since $typeMap is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $typeMap to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
240
            $optionDefinition = static::$typeMap[$option];
0 ignored issues
show
Bug introduced by
Since $typeMap is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $typeMap to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
241
        }
242
243
        $optionClassName = sprintf('\Jgut\Spiral\Option\\Option%s', ucfirst($optionDefinition['type']));
244
        $optionClass = new $optionClassName($option);
245
246
        switch (strtolower($optionDefinition['type'])) {
247
            case 'regex':
248
                /** @var \Jgut\Spiral\Option\OptionRegex $optionClass */
249
                $optionClass->setRegex($optionDefinition['regex']);
250
                if (array_key_exists('message', $optionDefinition)) {
251
                    $optionClass->setMessage($optionDefinition['message']);
252
                }
253
                break;
254
255
            case 'int':
256
                /** @var \Jgut\Spiral\Option\OptionInt $optionClass */
257
                $optionClass->setMin(array_key_exists('min', $optionDefinition) ? $optionDefinition['min'] : 0);
258
                if (array_key_exists('max', $optionDefinition)) {
259
                    $optionClass->setMax($optionDefinition['max']);
260
                }
261
                break;
262
263
            case 'callback':
264
                $optionClass = static::configureCallback($optionClass, $option);
265
                break;
266
        }
267
268
        $optionClass->setValue($value);
269
270
        return $optionClass;
271
    }
272
273
    /**
274
     * Get mapped option.
275
     *
276
     * @param int|string $option
277
     *
278
     * @return int
279
     *
280
     * @throws \Jgut\Spiral\Exception\OptionException
281
     */
282
    public static function getOptionKey($option)
283
    {
284
        if (is_string($option)) {
285
            $option = strtolower(preg_replace('/[ _]+/', '-', trim($option)));
286
            if (array_key_exists($option, static::$aliasMap)) {
0 ignored issues
show
Bug introduced by
Since $aliasMap is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $aliasMap to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
287
                $option = static::$aliasMap[strtolower($option)];
0 ignored issues
show
Bug introduced by
Since $aliasMap is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $aliasMap to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
288
            }
289
        }
290
291
        $curlConstants = [];
292
        foreach (get_defined_constants(true)['curl'] as $key => $val) {
293
            if (strpos($key, 'CURLOPT_') === 0) {
294
                $curlConstants[] = $val;
295
            }
296
        }
297
298
        if (!in_array($option, $curlConstants, true)) {
299
            throw new OptionException(sprintf('"%s" is not valid cURL option', $option));
300
        }
301
302
        return $option;
303
    }
304
305
    /**
306
     * Configure option callback.
307
     *
308
     * @param \Jgut\Spiral\Option\OptionCallback $optionClass
309
     * @param int                                $option
310
     *
311
     * @return \Jgut\Spiral\Option\OptionCallback
312
     */
313
    protected static function configureCallback(OptionCallback $optionClass, $option)
314
    {
315
        switch ($option) {
316
            case CURLOPT_HTTP_VERSION:
317
                $optionClass->setCallback(function ($value) {
318
                    $value = number_format((float) $value, 1, '.', '');
319
320
                    if (!preg_match('/^1.(0|1)$/', $value)) {
321
                        throw new OptionException(sprintf('"%s" is not a valid HTTP version', $value));
322
                    }
323
324
                    return constant('CURL_HTTP_VERSION_' . str_replace('.', '_', $value));
325
                });
326
                break;
327
328
            case CURLOPT_COOKIE:
329
                $optionClass->setCallback(function ($value) {
330
                    if (is_array($value)) {
331
                        $value = http_build_query($value, '', '; ');
332
                    }
333
334
                    return $value;
335
                });
336
                break;
337
        }
338
339
        return $optionClass;
340
    }
341
}
342