Completed
Push — develop ( 4b49c4...89d32a )
by Jaap
09:06 queued 05:30
created

Dsn::parse()   C

Complexity

Conditions 10
Paths 9

Size

Total Lines 50
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 10

Importance

Changes 0
Metric Value
cc 10
eloc 28
nc 9
nop 1
dl 0
loc 50
ccs 27
cts 27
cp 1
crap 10
rs 5.7647
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file is part of phpDocumentor.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @copyright 2010-2015 Mike van Riel<[email protected]>
9
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
10
 * @link      http://phpdoc.org
11
 */
12
13
namespace phpDocumentor\DomainModel;
14
15
/**
16
 * Value Object for DSN.
17
 */
18
final class Dsn
19
{
20
    /** @var string */
21
    private $dsn;
22
23
    /** @var string */
24
    private $scheme;
25
26
    /** @var string */
27
    private $host;
28
29
    /** @var int */
30
    private $port;
31
32
    /** @var string */
33
    private $user;
34
35
    /** @var string */
36
    private $password;
37
38
    /** @var string */
39
    private $path;
40
41
    /** @var string[]  */
42
    private $query = [];
43
44
    /** @var string[]  */
45
    private $parameters = [];
46
47
    //@codingStandardsIgnoreStart
48
    const WINDOWS_DSN = '~(^((?<scheme>file):\\/\\/)?(?<path>((?:[a-z]|[A-Z]):(?=\\\\(?![\\0-\\37<>:"/\\\\|?*])|\\/(?![\\0-\\37<>:"/\\\\|?*])|$)|^\\\\(?=[\\\\\\/][^\\0-\\37<>:"/\\\\|?*]+)|^(?=(\\\\|\\/)$)|^\\.(?=(\\\\|\\/)$)|^\\.\\.(?=(\\\\|\\/)$)|^(?=(\\\\|\\/)[^\\0-\\37<>:"/\\\\|?*]+)|^\\.(?=(\\\\|\\/)[^\\0-\\37<>:"/\\\\|?*]+)|^\\.\\.(?=(\\\\|\\/)[^\\0-\\37<>:"/\\\\|?*]+))((\\\\|\\/)[^\\0-\\37<>:"/\\\\|?*]+|(\\\\|\\/)$)*()))$~';
49
    //@codingStandardsIgnoreEnd
50
51
    /**
52
     * Initializes the Dsn
53
     *
54
     * @param string $dsn
55
     */
56 9
    public function __construct($dsn)
57
    {
58 9
        $this->parse($dsn);
59 5
    }
60
61
    /**
62
     * Returns a string representation of the DSN.
63
     *
64
     * @return string
65
     */
66 4
    public function __toString()
67
    {
68 4
        return $this->dsn;
69
    }
70
71
    /**
72
     * Returns the scheme part of the DSN
73
     *
74
     * @return string
75
     */
76 6
    public function getScheme()
77
    {
78 6
        return $this->scheme;
79
    }
80
81
    /**
82
     * Returns the host part of the DSN
83
     *
84
     * @return string
85
     */
86 4
    public function getHost()
87
    {
88 4
        return $this->host;
89
    }
90
91
    /**
92
     * Returns the port part of the DSN
93
     *
94
     * @return int
95
     */
96 5
    public function getPort()
97
    {
98 5
        return $this->port;
99
    }
100
101
    /**
102
     * Returns the username part of the DSN
103
     *
104
     * @return string
105
     */
106 1
    public function getUsername()
107
    {
108 1
        return $this->user;
109
    }
110
111
    /**
112
     * Returns the password part of the DSN
113
     *
114
     * @return string
115
     */
116 1
    public function getPassword()
117
    {
118 1
        return $this->password;
119
    }
120
121
    /**
122
     * Returns the path part of the DSN
123
     *
124
     * @return string
125
     */
126 4
    public function getPath()
127
    {
128 4
        return new Path($this->path);
129
    }
130
131
    /**
132
     * Returns the query part of the DSN
133
     *
134
     * @return string[]
135
     */
136 1
    public function getQuery()
137
    {
138 1
        return $this->query;
139
    }
140
141
    /**
142
     * Returns the parameters part of the DSN
143
     *
144
     * @return string[]
145
     */
146 1
    public function getParameters()
147
    {
148 1
        return $this->parameters;
149
    }
150
151
    /**
152
     * Parses the given DSN
153
     *
154
     * @param string $dsn
155
     * @return void
156
     */
157 9
    private function parse($dsn)
158
    {
159 9
        if (! is_string($dsn)) {
160 1
            throw new \InvalidArgumentException(
161 1
                sprintf('"%s" is not a valid DSN.', var_export($dsn, true))
162
            );
163
        }
164
165 8
        $dsnParts = explode(';', $dsn);
166 8
        $location = $dsnParts[0];
167 8
        unset($dsnParts[0]);
168 8
        $locationParts = parse_url($location);
169
170
        if (
171 8
            $locationParts === false ||
172 8
            (array_key_exists('scheme', $locationParts) && \strlen($locationParts['scheme']) === 1)
173
        ) {
174 2
            preg_match(static::WINDOWS_DSN, $dsn, $locationParts);
175
        }
176
177
        if (
178 8
            ! array_key_exists('scheme', $locationParts) ||
179 8
            ($locationParts['scheme'] === '' && array_key_exists('path', $locationParts))
180
        ) {
181 2
            $locationParts['scheme'] = 'file';
182 2
            $location = 'file://' . $location;
183
        }
184
185 8
        if (!filter_var($location, FILTER_VALIDATE_URL) && !preg_match(static::WINDOWS_DSN, $location)) {
186 1
            throw new \InvalidArgumentException(
187 1
                sprintf('"%s" is not a valid DSN.', $dsn)
188
            );
189
        }
190
191 7
        $this->parseDsn($location, $dsnParts);
192
193 7
        $this->parseScheme($locationParts);
194
195 6
        $this->parseHostAndPath($locationParts);
196
197 6
        $this->parsePort($locationParts);
198
199 6
        $this->user = $locationParts['user'] ?? "";
200
201 6
        $this->password = $locationParts['pass'] ?? "";
202
203 6
        $this->parseQuery($locationParts);
204
205 5
        $this->parseParameters($dsnParts);
206 5
    }
207
208
    /**
209
     * Reconstructs the original DSN but
210
     * when scheme was omitted in the original DSN, it will now be file://
211
     *
212
     * @param string $location
213
     * @param string[] $dsnParts
214
     * @return void
215
     */
216 7
    private function parseDsn($location, array $dsnParts)
217
    {
218 7
        array_splice($dsnParts, 0, 0, $location);
219 7
        $this->dsn = implode(";", $dsnParts);
220 7
    }
221
222
    /**
223
     * validates and sets the scheme property
224
     *
225
     * @param string[] $locationParts
226
     * @return void
227
     */
228 7
    private function parseScheme(array $locationParts)
229
    {
230 7
        if (! $this->isValidScheme($locationParts['scheme'])) {
231 1
            throw new \InvalidArgumentException(
232 1
                sprintf('"%s" is not a valid scheme.', $locationParts['scheme'])
233
            );
234
        }
235 6
        $this->scheme = strtolower($locationParts['scheme']);
236 6
    }
237
238
    /**
239
     * Validated provided scheme.
240
     *
241
     * @param string $scheme
242
     * @return bool
243
     */
244 7
    private function isValidScheme(string $scheme): bool
245
    {
246 7
        $validSchemes = ['file', 'git+http', 'git+https'];
247 7
        return \in_array(\strtolower($scheme), $validSchemes, true);
248
    }
249
250
    /**
251
     * Validates and sets the host and path properties
252
     *
253
     * @param string[] $locationParts
254
     * @return void
255
     */
256 6
    private function parseHostAndPath(array $locationParts)
257
    {
258 6
        $path = $locationParts['path'] ?? "";
259 6
        $host = $locationParts['host'] ?? "";
260
261 6
        if ($this->getScheme() === 'file') {
262 3
            $this->path = $host . $path;
263
        } else {
264 3
            $this->host = $host;
265 3
            $this->path = $path;
266
        }
267 6
    }
268
269
    /**
270
     * Validates and sets the port property
271
     *
272
     * @param string[] $locationParts
273
     * @return void
274
     */
275 6
    private function parsePort(array $locationParts)
276
    {
277 6
        if (! isset($locationParts['port'])) {
278 5
            if ($this->getScheme() === 'git+http') {
279 2
                $this->port = 80;
280 4
            } elseif ($this->getScheme() === 'git+https') {
281 1
                $this->port = 443;
282
            } else {
283 5
                $this->port = 0;
284
            }
285
        } else {
286 1
            $this->port = $locationParts['port'];
0 ignored issues
show
Documentation Bug introduced by Marieke Bednarczyk
The property $port was declared of type integer, but $locationParts['port'] is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
287
        }
288 6
    }
289
290
    /**
291
     * validates and sets the query property
292
     *
293
     * @param string[] $locationParts
294
     * @return void
295
     */
296 6
    private function parseQuery(array $locationParts)
297
    {
298 6
        if (isset($locationParts['query'])) {
299 2
            $queryParts = explode('&', $locationParts['query']);
300 2
            foreach ($queryParts as $part) {
301 2
                $option = $this->splitKeyValuePair($part);
302
303 1
                $this->query[$option[0]] = $option[1];
304
            }
305
        }
306 5
    }
307
308
    /**
309
     * validates and sets the parameters property
310
     *
311
     * @param string[] $dsnParts
312
     * @return void
313
     */
314 5
    private function parseParameters(array $dsnParts)
315
    {
316 5
        foreach ($dsnParts as $part) {
317 1
            $option = $this->splitKeyValuePair($part);
318
319 1
            $this->parameters[$option[0]] = $option[1];
320
        }
321 5
    }
322
323
    /**
324
     * Splits a key-value pair
325
     *
326
     * @param string $pair
327
     * @return string[] $option
328
     */
329 2
    private function splitKeyValuePair($pair)
330
    {
331 2
        $option = explode('=', $pair);
332 2
        if (count($option) !== 2) {
333 1
            throw new \InvalidArgumentException(
334 1
                sprintf('"%s" is not a valid query or parameter.', $pair)
335
            );
336
        }
337
338 1
        return $option;
339
    }
340
}
341