Completed
Push — develop ( d73a05...7ce957 )
by Mike
07:24
created

Dsn   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 289
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 289
rs 9.52
c 0
b 0
f 0
wmc 36
lcom 1
cbo 1

19 Methods

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