Completed
Push — master ( 0d7fa9...b2be26 )
by Julián
02:20
created

OptionFactory::getOptionKey()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
c 4
b 1
f 0
dl 0
loc 23
rs 8.5906
cc 5
eloc 13
nc 6
nop 1
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
        // Convert Unix newlines to CRLF newlines on transfers
89
        CURLOPT_CRLF              => ['type' => 'bool'],
90
        // Include request header in the output
91
        CURLINFO_HEADER_OUT       => ['type' => 'bool'],
92
        // Include response header in the output
93
        CURLOPT_HEADER            => ['type' => 'bool'],
94
        // Return the transfer as a string instead of outputting
95
        CURLOPT_RETURNTRANSFER    => ['type' => 'bool'],
96
        // Output verbose information
97
        CURLOPT_VERBOSE           => ['type' => 'bool'],
98
        // False to stop cURL from verifying the peer's certificate
99
        CURLOPT_SSL_VERIFYPEER    => ['type' => 'bool'],
100
        // Follow any Location headers sent by server
101
        CURLOPT_FOLLOWLOCATION    => ['type' => 'bool'],
102
        // Automatically set the http referer header when following a redirect
103
        CURLOPT_AUTOREFERER       => ['type' => 'bool'],
104
        // Keep sending the username and password when following locations
105
        CURLOPT_UNRESTRICTED_AUTH => ['type' => 'bool'],
106
        // Get HTTP header for modification date of file
107
        CURLOPT_FILETIME          => ['type' => 'bool'],
108
        // Automatically close connection after processing
109
        CURLOPT_FORBID_REUSE      => ['type' => 'bool'],
110
        // Force to use new connection instead of cached
111
        CURLOPT_FRESH_CONNECT     => ['type' => 'bool'],
112
        // Scan ~/.netrc file for user credentials
113
        CURLOPT_NETRC             => ['type' => 'bool'],
114
115
        // Integer
116
        // Number of seconds to wait while trying to connect. 0 to wait indefinitely
117
        CURLOPT_CONNECTTIMEOUT => ['type' => 'int'],
118
        // Maximum number of seconds to allow cURL functions to execute
119
        CURLOPT_TIMEOUT        => ['type' => 'int'],
120
        // The maximum amount of HTTP redirections to follow
121
        CURLOPT_MAXREDIRS      => ['type' => 'int'],
122
        // Alternative port number to connect to
123
        CURLOPT_PORT           => ['type' => 'int', 'max' => 99999],
124
        // Size of the buffer for each read
125
        CURLOPT_BUFFERSIZE     => ['type' => 'int'],
126
127
        // String
128
        // Contents of the "User-Agent: " header to be used in a HTTP request
129
        CURLOPT_USERAGENT => ['type' => 'string'],
130
        // Contents of the "Referer: " header to be used in a HTTP request
131
        CURLOPT_REFERER   => ['type' => 'string'],
132
        // Contents of the "Accept-Encoding: " header. This enables decoding of the response
133
        CURLOPT_ENCODING  => ['type' => 'string'],
134
        // Alternative location to output errors
135
        CURLOPT_STDERR    => ['type' => 'string'],
136
137
        // File
138
        // Name of the file containing the cookie data
139
        CURLOPT_COOKIEFILE => ['type' => 'file'],
140
        // Name of a file to save all internal cookies to when the handle is closed
141
        CURLOPT_COOKIEJAR  => ['type' => 'file'],
142
143
        // Regex
144
        // Which SSL version (2 or 3) to use
145
        CURLOPT_SSLVERSION        => [
146
            'type' => 'regex',
147
            'regex' => '/^[0-6]$/',
148
            'message' => '"%s" is not valid SSL version'
149
        ],
150
        // Verify existence of a common name in peer certificate, and matches hostname
151
        CURLOPT_SSL_VERIFYHOST => [
152
            'type' => 'regex',
153
            'regex' => '/^[12]$/',
154
            'message' => '"%s" is not valid SSL verify host value'
155
        ],
156
        // Bit mask to maintain redirection type
157
        CURLOPT_POSTREDIR => [
158
            'type' => 'regex',
159
            'regex' => '/^[124]$/',
160
            'message' => '"%s" is not valid POST redirection value'
161
        ],
162
        // Username and password formatted as "[username]:[password]" to use for the connection
163
        CURLOPT_USERPWD => [
164
            'type' => 'regex',
165
            'regex' => '/^[^\n:]+:[^\n:]+$/',
166
            'message' => '"%s" is not a valid CURLOPT_USERPWD value'
167
        ],
168
169
        // Callback
170
        /*
171
        HTTP authentication method(s) to use:
172
            CURLAUTH_BASIC, CURLAUTH_DIGEST, CURLAUTH_GSSNEGOTIATE, CURLAUTH_NTLM, CURLAUTH_ANY, CURLAUTH_ANYSAFE
173
        Currently only CURLAUTH_BASIC is available and implemented as boolean
174
        */
175
        CURLOPT_HTTPAUTH     => ['type' => 'callback'],
176
        // Which HTTP version to use. "1.0" for CURL_HTTP_VERSION_1_0 or "1.1" for CURL_HTTP_VERSION_1_1
177
        CURLOPT_HTTP_VERSION => ['type' => 'callback'],
178
        // Contents of the "Cookie: " header to be used in the HTTP request. Can be an array
179
        CURLOPT_COOKIE       => ['type' => 'callback'],
180
    ];
181
182
    /**
183
     * Build cURL option.
184
     *
185
     * @param int|string $option
186
     * @param mixed      $value
187
     *
188
     * @return \Jgut\Spiral\Option
189
     *
190
     * @throws \Jgut\Spiral\Exception\OptionException
191
     */
192
    public static function build($option, $value)
193
    {
194
        $optionDefinition = ['type' => ''];
195
196
        $option = static::getOptionKey($option);
197
        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...
198
            $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...
199
        }
200
201
        $optionClassName = sprintf('\Jgut\Spiral\Option\\Option%s', ucfirst($optionDefinition['type']));
202
        $optionClass = new $optionClassName($option);
203
204
        switch (strtolower($optionDefinition['type'])) {
205
            case 'regex':
206
                /** @var \Jgut\Spiral\Option\OptionRegex $optionClass */
207
                $optionClass->setRegex($optionDefinition['regex']);
208
                if (array_key_exists('message', $optionDefinition)) {
209
                    $optionClass->setMessage($optionDefinition['message']);
210
                }
211
                break;
212
213
            case 'int':
214
                /** @var \Jgut\Spiral\Option\OptionInt $optionClass */
215
                $optionClass->setMin(array_key_exists('min', $optionDefinition) ? $optionDefinition['min'] : 0);
216
                if (array_key_exists('max', $optionDefinition)) {
217
                    $optionClass->setMax($optionDefinition['max']);
218
                }
219
                break;
220
221
            case 'callback':
222
                $optionClass = static::configureCallback($optionClass, $option);
223
                break;
224
        }
225
226
        $optionClass->setValue($value);
227
228
        return $optionClass;
229
    }
230
231
    /**
232
     * Get mapped option.
233
     *
234
     * @param int|string $option
235
     *
236
     * @return int
237
     *
238
     * @throws \Jgut\Spiral\Exception\OptionException
239
     */
240
    public static function getOptionKey($option)
241
    {
242
        if (is_string($option)) {
243
            $option = strtolower(preg_replace('/[ _]+/', '-', trim($option)));
244
            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...
245
                $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...
246
            }
247
        }
248
249
        $curlConstants = array_filter(
250
            get_defined_constants(true)['curl'],
251
            function ($val, $key) {
252
                return is_int($val) && strpos($key, 'CURLOPT_') === 0;
253
            },
254
            ARRAY_FILTER_USE_BOTH
255
        );
256
257
        if (!in_array($option, $curlConstants, true)) {
258
            throw new OptionException(sprintf('"%s" is not valid cURL option', $option));
259
        }
260
261
        return $option;
262
    }
263
264
    /**
265
     * Configure option callback.
266
     *
267
     * @param \Jgut\Spiral\Option\OptionCallback $optionClass
268
     * @param                                    $option
269
     *
270
     * @return \Jgut\Spiral\Option\OptionCallback
271
     */
272
    protected static function configureCallback(OptionCallback $optionClass, $option)
273
    {
274
        switch ($option) {
275
            case CURLOPT_HTTPAUTH:
276
                $optionClass->setCallback(function ($value) {
277
                    return $value === false ? false : CURLAUTH_BASIC;
278
                });
279
                break;
280
281
            case CURLOPT_HTTP_VERSION:
282
                $optionClass->setCallback(function ($value) {
283
                    $value = number_format((float) $value, 1, '.', '');
284
285
                    if (!preg_match('/^1.(0|1)$/', $value)) {
286
                        throw new OptionException(sprintf('"%s" is not a valid HTTP version', $value));
287
                    }
288
289
                    return constant('CURL_HTTP_VERSION_' . str_replace('.', '_', $value));
290
                });
291
                break;
292
293
            case CURLOPT_COOKIE:
294
                $optionClass->setCallback(function ($value) {
295
                    if (is_array($value)) {
296
                        $value = http_build_query($value, '', '; ');
297
                    }
298
299
                    return $value;
300
                });
301
                break;
302
        }
303
304
        return $optionClass;
305
    }
306
}
307