Completed
Push — develop ( b67add...4b36a1 )
by Jaap
03:57 queued 24s
created

Dsn::isValidScheme()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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