Uri::__construct()   B
last analyzed

Complexity

Conditions 8
Paths 34

Size

Total Lines 17
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 8

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 13
c 1
b 0
f 0
nc 34
nop 1
dl 0
loc 17
ccs 14
cts 14
cp 1
crap 8
rs 8.4444
1
<?php
2
3
namespace PTS\Psr7;
4
5
use InvalidArgumentException;
6
use Psr\Http\Message\UriInterface;
7
use function is_string;
8
use function parse_url;
9
use function preg_replace_callback;
10
use function rawurlencode;
11
use function sprintf;
12
13
class Uri implements UriInterface
14
{
15
    use LowercaseTrait;
16
17
    protected const SCHEMES = ['http' => 80, 'https' => 443];
18
    protected const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
19
    protected const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
20
21
    protected string $scheme = '';
22
    protected string $userInfo = '';
23
    protected string $host = '';
24
25
    protected ?int $port = null;
26
    protected string $path = '';
27
    protected string $query = '';
28
    protected string $fragment = '';
29
30 91
    public function __construct(string $uri = '')
31
    {
32 91
        if ('' !== $uri) {
33 71
            $parts = parse_url($uri);
34 71
            if ($parts === false) {
35 2
                throw new InvalidArgumentException(sprintf('Unable to parse URI: "%s"', $uri));
36
            }
37
38 69
            $this->withScheme($parts['scheme'] ?? '');
39 69
            $this->userInfo = $parts['user'] ?? '';
40 69
            $this->withHost($parts['host'] ?? '');
41 69
            $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null;
42 68
            $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
43 68
            $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : '';
44 68
            $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : '';
45 68
            if (isset($parts['pass'])) {
46 2
                $this->userInfo .= ':' . $parts['pass'];
47
            }
48
        }
49 88
    }
50
51 40
    public function __toString(): string
52
    {
53 40
        return self::createUriString($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment);
54
    }
55
56 8
    public function getScheme(): string
57
    {
58 8
        return $this->scheme;
59
    }
60
61 45
    public function getAuthority(): string
62
    {
63 45
        if ('' === $this->host) {
64 28
            return '';
65
        }
66
67 17
        $authority = $this->host;
68 17
        if ('' !== $this->userInfo) {
69 4
            $authority = $this->userInfo . '@' . $authority;
70
        }
71
72 17
        if (null !== $this->port) {
73 4
            $authority .= ':' . $this->port;
74
        }
75
76 17
        return $authority;
77
    }
78
79 6
    public function getUserInfo(): string
80
    {
81 6
        return $this->userInfo;
82
    }
83
84 46
    public function getHost(): string
85
    {
86 46
        return $this->host;
87
    }
88
89 22
    public function getPort(): ?int
90
    {
91 22
        return $this->port;
92
    }
93
94 20
    public function getPath(): string
95
    {
96 20
        return $this->path;
97
    }
98
99 28
    public function getQuery(): string
100
    {
101 28
        return $this->query;
102
    }
103
104 12
    public function getFragment(): string
105
    {
106 12
        return $this->fragment;
107
    }
108
109 72
    public function withScheme($scheme): self
110
    {
111 72
        $this->scheme = self::lowercase($scheme);
112 72
        $this->port = $this->port ? $this->filterPort($this->port) : null;
113
114 72
        return $this;
115
    }
116
117 3
    public function withUserInfo($user, $password = null): self
118
    {
119 3
        $info = $user;
120 3
        if (null !== $password && '' !== $password) {
121 3
            $info .= ':' . $password;
122
        }
123
124 3
        $this->userInfo = $info;
125
126 3
        return $this;
127
    }
128
129 72
    public function withHost($host): self
130
    {
131 72
        $this->host = self::lowercase($host);
132 72
        return $this;
133
    }
134
135 7
    public function withPort($port): self
136
    {
137 7
        $this->port = $port === null ? null : $this->filterPort($port);
138 5
        return $this;
139
    }
140
141 7
    public function withPath($path): self
142
    {
143 7
        $path = $this->filterPath($path);
144 6
        if ($this->path !== $path) {
145 6
            $this->path = $path;
146
        }
147
148 6
        return $this;
149
    }
150
151 4
    public function withQuery($query): self
152
    {
153 4
        $query = $this->filterQueryAndFragment($query);
154 3
        if ($this->query !== $query) {
155 3
            $this->query = $query;
156
        }
157
158 3
        return $this;
159
    }
160
161 4
    public function withFragment($fragment): self
162
    {
163 4
        $fragment = $this->filterQueryAndFragment($fragment);
164 3
        if ($this->fragment !== $fragment) {
165 3
            $this->fragment = $fragment;
166
        }
167
168 3
        return $this;
169
    }
170
171 40
    private static function createUriString(
172
        string $scheme,
173
        string $authority,
174
        string $path,
175
        string $query,
176
        string $fragment
177
    ): string {
178 40
        $uri = '';
179 40
        if ('' !== $scheme) {
180 13
            $uri .= $scheme . ':';
181
        }
182
183 40
        if ('' !== $authority) {
184 14
            $uri .= '//' . $authority;
185
        }
186
187 40
        if ('' !== $path) {
188 26
            if ('/' !== $path[0]) {
189 7
                if ('' !== $authority) {
190
                    // If the path is rootless and an authority is present, the path MUST be prefixed by "/"
191 7
                    $path = '/' . $path;
192
                }
193 19
            } elseif (isset($path[1]) && '/' === $path[1]) {
194 1
                if ('' === $authority) {
195
                    // If the path is starting with more than one "/" and no authority is present, the
196
                    // starting slashes MUST be reduced to one.
197 1
                    $path = '/' . \ltrim($path, '/');
198
                }
199
            }
200
201 26
            $uri .= $path;
202
        }
203
204 40
        if ('' !== $query) {
205 14
            $uri .= '?' . $query;
206
        }
207
208 40
        if ('' !== $fragment) {
209 13
            $uri .= '#' . $fragment;
210
        }
211
212 40
        return $uri;
213
    }
214
215 10
    private static function isNonStandardPort(string $scheme, int $port): bool
216
    {
217 10
        return !isset(self::SCHEMES[$scheme]) || $port !== self::SCHEMES[$scheme];
218
    }
219
220 13
    protected function filterPort(int $port): ?int
221
    {
222 13
        if ($port < 1 || $port > 65535) {
223 3
            throw new InvalidArgumentException(sprintf('Invalid port: %d. Must be between 0 and 65535', $port));
224
        }
225
226 10
        return self::isNonStandardPort($this->scheme, $port) ? $port : null;
227
    }
228
229 61
    private function filterPath($path): string
230
    {
231 61
        if (!is_string($path)) {
232 1
            throw new InvalidArgumentException('Path must be a string');
233
        }
234
235 60
        return preg_replace_callback('/(?:[^' .
236 60
            self::CHAR_UNRESERVED .
237 60
            self::CHAR_SUB_DELIMS .
238 60
            '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $path);
239
    }
240
241 26
    private function filterQueryAndFragment($str): string
242
    {
243 26
        if (!is_string($str)) {
244 2
            throw new InvalidArgumentException('Query and fragment must be a string');
245
        }
246
247 24
        return preg_replace_callback('/(?:[^' .
248 24
            self::CHAR_UNRESERVED .
249 24
            self::CHAR_SUB_DELIMS .
250 24
            '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $str);
251
    }
252
253 6
    private static function rawUrlEncodeMatchZero(array $match): string
0 ignored issues
show
Unused Code introduced by
The method rawUrlEncodeMatchZero() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
254
    {
255 6
        return rawurlencode($match[0]);
256
    }
257
}