Completed
Push — develop ( 4b36a1...568a5c )
by Jaap
07:06
created

Dsn::splitKeyValuePair()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 11
ccs 6
cts 6
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
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
    const WINDOWS_DSN = '~(^((?<scheme>file):\\/\\/)?(?<path>((?:[a-z]|[A-Z]):(?=\\\\(?![\\0-\\37<>:"/\\\\|?*])|\\/(?![\\0-\\37<>:"/\\\\|?*])|$)|^\\\\(?=[\\\\\\/][^\\0-\\37<>:"/\\\\|?*]+)|^(?=(\\\\|\\/)$)|^\\.(?=(\\\\|\\/)$)|^\\.\\.(?=(\\\\|\\/)$)|^(?=(\\\\|\\/)[^\\0-\\37<>:"/\\\\|?*]+)|^\\.(?=(\\\\|\\/)[^\\0-\\37<>:"/\\\\|?*]+)|^\\.\\.(?=(\\\\|\\/)[^\\0-\\37<>:"/\\\\|?*]+))((\\\\|\\/)[^\\0-\\37<>:"/\\\\|?*]+|(\\\\|\\/)$)*()))$~';
48
49
    /**
50
     * Initializes the Dsn
51
     *
52
     * @param string $dsn
53
     */
54 9
    public function __construct($dsn)
55
    {
56 9
        $this->parse($dsn);
57 5
    }
58
59
    /**
60
     * Returns a string representation of the DSN.
61
     *
62
     * @return string
63
     */
64 4
    public function __toString()
65
    {
66 4
        return $this->dsn;
67
    }
68
69
    /**
70
     * Returns the scheme part of the DSN
71
     *
72
     * @return string
73
     */
74 6
    public function getScheme()
75
    {
76 6
        return $this->scheme;
77
    }
78
79
    /**
80
     * Returns the host part of the DSN
81
     *
82
     * @return string
83
     */
84 4
    public function getHost()
85
    {
86 4
        return $this->host;
87
    }
88
89
    /**
90
     * Returns the port part of the DSN
91
     *
92
     * @return int
93
     */
94 5
    public function getPort()
95
    {
96 5
        return $this->port;
97
    }
98
99
    /**
100
     * Returns the username part of the DSN
101
     *
102
     * @return string
103
     */
104 1
    public function getUsername()
105
    {
106 1
        return $this->user;
107
    }
108
109
    /**
110
     * Returns the password part of the DSN
111
     *
112
     * @return string
113
     */
114 1
    public function getPassword()
115
    {
116 1
        return $this->password;
117
    }
118
119
    /**
120
     * Returns the path part of the DSN
121
     *
122
     * @return string
123
     */
124 4
    public function getPath()
125
    {
126 4
        return new Path($this->path);
127
    }
128
129
    /**
130
     * Returns the query part of the DSN
131
     *
132
     * @return string[]
133
     */
134 1
    public function getQuery()
135
    {
136 1
        return $this->query;
137
    }
138
139
    /**
140
     * Returns the parameters part of the DSN
141
     *
142
     * @return string[]
143
     */
144 1
    public function getParameters()
145
    {
146 1
        return $this->parameters;
147
    }
148
149
    /**
150
     * Parses the given DSN
151
     *
152
     * @param string $dsn
153
     * @return void
154
     */
155 9
    private function parse($dsn)
156
    {
157 9
        if (! is_string($dsn)) {
158 1
            throw new \InvalidArgumentException(
159 1
                sprintf('"%s" is not a valid DSN.', var_export($dsn, true))
160
            );
161
        }
162
163 8
        $dsnParts = explode(';', $dsn);
164 8
        $location = $dsnParts[0];
165 8
        unset($dsnParts[0]);
166 8
        $locationParts = parse_url($location);
167
168 8
        if ($locationParts === false || (array_key_exists('scheme', $locationParts) && \strlen($locationParts['scheme']) === 1)) {
169 2
            preg_match(static::WINDOWS_DSN, $dsn, $locationParts);
170
        }
171
172 8
        if (! array_key_exists('scheme', $locationParts) || ($locationParts['scheme'] === '' && array_key_exists('path',$locationParts)) ) {
0 ignored issues
show
Coding Style introduced by
Expected 0 spaces before closing bracket; 1 found
Loading history...
173 2
            $locationParts['scheme'] = 'file';
174 2
            $location = 'file://' . $location;
175
        }
176
177 8
        if (!filter_var($location, FILTER_VALIDATE_URL) && !preg_match(static::WINDOWS_DSN, $location)) {
178 1
            throw new \InvalidArgumentException(
179 1
                sprintf('"%s" is not a valid DSN.', $dsn)
180
            );
181
        }
182
183 7
        $this->parseDsn($location, $dsnParts);
184
185 7
        $this->parseScheme($locationParts);
186
187 6
        $this->parseHostAndPath($locationParts);
188
189 6
        $this->parsePort($locationParts);
190
191 6
        $this->user = $locationParts['user'] ?? "";
192
193 6
        $this->password = $locationParts['pass'] ?? "";
194
195 6
        $this->parseQuery($locationParts);
196
197 5
        $this->parseParameters($dsnParts);
198 5
    }
199
200
    /**
201
     * Reconstructs the original DSN but
202
     * when scheme was omitted in the original DSN, it will now be file://
203
     *
204
     * @param string $location
205
     * @param string[] $dsnParts
206
     * @return void
207
     */
208 7
    private function parseDsn($location, array $dsnParts)
209
    {
210 7
        array_splice($dsnParts, 0, 0, $location);
211 7
        $this->dsn = implode(";", $dsnParts);
212 7
    }
213
214
    /**
215
     * validates and sets the scheme property
216
     *
217
     * @param string[] $locationParts
218
     * @return void
219
     */
220 7
    private function parseScheme(array $locationParts)
221
    {
222 7
        if (! $this->isValidScheme($locationParts['scheme'])) {
223 1
            throw new \InvalidArgumentException(
224 1
                sprintf('"%s" is not a valid scheme.', $locationParts['scheme'])
225
            );
226
        }
227 6
        $this->scheme = strtolower($locationParts['scheme']);
228 6
    }
229
230
    /**
231
     * Validated provided scheme.
232
     *
233
     * @param string $scheme
234
     * @return bool
235
     */
236 7
    private function isValidScheme(string $scheme): bool
237
    {
238 7
        $validSchemes = ['file', 'git+http', 'git+https'];
239 7
        return \in_array(\strtolower($scheme), $validSchemes, true);
240
    }
241
242
    /**
243
     * Validates and sets the host and path properties
244
     *
245
     * @param string[] $locationParts
246
     * @return void
247
     */
248 6
    private function parseHostAndPath(array $locationParts)
249
    {
250 6
        $path = $locationParts['path'] ?? "";
251 6
        $host = $locationParts['host'] ?? "";
252
253 6
        if ($this->getScheme() === 'file') {
254 3
            $this->path = $host . $path;
255
        } else {
256 3
            $this->host = $host;
257 3
            $this->path = $path;
258
        }
259 6
    }
260
261
    /**
262
     * Validates and sets the port property
263
     *
264
     * @param string[] $locationParts
265
     * @return void
266
     */
267 6
    private function parsePort(array $locationParts)
268
    {
269 6
        if (! isset($locationParts['port'])) {
270 5
            if ($this->getScheme() === 'git+http') {
271 2
                $this->port = 80;
272 4
            } elseif ($this->getScheme() === 'git+https') {
273 1
                $this->port = 443;
274
            } else {
275 5
                $this->port = 0;
276
            }
277
        } else {
278 1
            $this->port = $locationParts['port'];
0 ignored issues
show
Documentation Bug introduced by
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...
279
        }
280 6
    }
281
282
    /**
283
     * validates and sets the query property
284
     *
285
     * @param string[] $locationParts
286
     * @return void
287
     */
288 6
    private function parseQuery(array $locationParts)
289
    {
290 6
        if (isset($locationParts['query'])) {
291 2
            $queryParts = explode('&', $locationParts['query']);
292 2
            foreach ($queryParts as $part) {
293 2
                $option = $this->splitKeyValuePair($part);
294
295 1
                $this->query[$option[0]] = $option[1];
296
            }
297
        }
298 5
    }
299
300
    /**
301
     * validates and sets the parameters property
302
     *
303
     * @param string[] $dsnParts
304
     * @return void
305
     */
306 5
    private function parseParameters(array $dsnParts)
307
    {
308 5
        foreach ($dsnParts as $part) {
309 1
            $option = $this->splitKeyValuePair($part);
310
311 1
            $this->parameters[$option[0]] = $option[1];
312
        }
313 5
    }
314
315
    /**
316
     * Splits a key-value pair
317
     *
318
     * @param string $pair
319
     * @return string[] $option
320
     */
321 2
    private function splitKeyValuePair($pair)
322
    {
323 2
        $option = explode('=', $pair);
324 2
        if (count($option) !== 2) {
325 1
            throw new \InvalidArgumentException(
326 1
                sprintf('"%s" is not a valid query or parameter.', $pair)
327
            );
328
        }
329
330 1
        return $option;
331
    }
332
}
333