Passed
Push — master ( 4ac306...d2aa89 )
by Mihail
05:30
created

Uri::isStandardPort()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 2
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
 *
11
 */
12
13
namespace Koded\Http;
14
15
use InvalidArgumentException;
16
use Koded\Http\Interfaces\HttpStatus;
17
use Psr\Http\Message\UriInterface;
18
use Throwable;
19
20
class Uri implements UriInterface
21
{
22
    /** @var string */
23
    private $scheme = '';
24
25
    /** @var string */
26
    private $host = '';
27
28
    /** @var int */
29
    private $port = 80;
30
31
    /** @var string */
32
    private $path = '';
33
34
    /** @var string */
35
    private $user = '';
36
37
    /** @var string */
38
    private $pass = '';
39
40
    /** @var string */
41
    private $fragment = '';
42
43
    /** @var string */
44
    private $query = '';
45
46 174
    public function __construct(string $uri)
47
    {
48 174
        $uri && $this->parse($uri);
49 173
    }
50
51 26
    public function __toString()
52
    {
53 26
        return sprintf('%s%s%s%s%s',
54 26
            $this->scheme ? $this->getScheme() . '://' : '',
55 26
            $this->getAuthority() ?: $this->getHostWithPort(),
56 26
            $this->getPath(),
57 26
            strlen($this->query) ? '?' . $this->query : '',
58 26
            strlen($this->fragment) ? '#' . $this->fragment : ''
59
        );
60
    }
61
62 26
    public function getScheme(): string
63
    {
64 26
        return strtolower($this->scheme);
65
    }
66
67 30
    public function getAuthority(): string
68
    {
69 30
        $userInfo = $this->getUserInfo();
70
71 30
        if (0 === strlen($userInfo)) {
72 26
            return '';
73
        }
74
75 6
        return $userInfo . '@' . $this->getHostWithPort();
76
    }
77
78 34
    public function getUserInfo(): string
79
    {
80 34
        if (0 === strlen($this->user)) {
81 30
            return '';
82
        }
83
84 8
        return trim($this->user . ':' . $this->pass, ':');
85
    }
86
87 135
    public function getHost(): string
88
    {
89 135
        return strtolower($this->host);
90
    }
91
92 9
    public function getPort(): ?int
93
    {
94 9
        if (!$this->scheme && !$this->port) {
95 1
            return null;
96
        }
97
98 8
        return $this->port;
99
    }
100
101 46
    public function getPath(): string
102
    {
103 46
        return $this->reduceSlashes($this->path);
104
    }
105
106 9
    public function getQuery(): string
107
    {
108 9
        return $this->query;
109
    }
110
111 8
    public function getFragment(): string
112
    {
113 8
        return $this->fragment;
114
    }
115
116 8
    public function withScheme($scheme): UriInterface
117
    {
118 8
        if (null !== $scheme && false === is_string($scheme)) {
119 4
            throw new InvalidArgumentException('Invalid URI scheme', 400);
120
        }
121
122 4
        $instance         = clone $this;
123 4
        $instance->scheme = (string)$scheme;
124
125 4
        return $instance;
126
    }
127
128 4
    public function withUserInfo($user, $password = null): UriInterface
129
    {
130 4
        $instance       = clone $this;
131 4
        $instance->user = (string)$user;
132 4
        $instance->pass = (string)$password;
133
134
        // If the path is rootless and an authority is present,
135
        // the path MUST be prefixed with "/"
136 4
        if ('/' !== ($instance->path[0] ?? '')) {
137 2
            $instance->path = '/' . $instance->path;
138
        }
139
140 4
        return $instance;
141
    }
142
143 7
    public function withHost($host): UriInterface
144
    {
145 7
        $instance       = clone $this;
146 7
        $instance->host = (string)$host;
147
148 7
        return $instance;
149
    }
150
151 6
    public function withPort($port): UriInterface
152
    {
153 6
        $instance = clone $this;
154
155 6
        if (null === $port) {
156 2
            $instance->port = null;
157
158 2
            return $instance;
159
        }
160
161 4
        if (false === is_int($port) || $port < 1) {
162 1
            throw new InvalidArgumentException('Invalid port');
163
        }
164
165 3
        $instance->port = $port;
166
167 3
        return $instance;
168
    }
169
170 4
    public function withPath($path): UriInterface
171
    {
172 4
        $instance       = clone $this;
173 4
        $instance->path = $this->fixPath((string)$path);
174
175 4
        return $instance;
176
    }
177
178 3
    public function withQuery($query): UriInterface
179
    {
180
        try {
181 3
            $query = rawurldecode($query);
182 1
        } catch (Throwable $e) {
183 1
            throw new InvalidArgumentException('The provided query string is invalid');
184
        }
185
186 2
        $instance        = clone $this;
187 2
        $instance->query = (string)$query;
188
189 2
        return $instance;
190
    }
191
192 3
    public function withFragment($fragment): UriInterface
193
    {
194 3
        $instance           = clone $this;
195 3
        $instance->fragment = str_replace(['#', '%23'], '', $fragment);
196
197 3
        return $instance;
198
    }
199
200 138
    private function parse(string $uri)
201
    {
202 138
        if (false === $parts = parse_url($uri)) {
203 3
            throw new InvalidArgumentException('Please provide a valid URI', HttpStatus::BAD_REQUEST);
204
        }
205
206 137
        foreach ($parts as $k => $v) {
207 137
            $this->$k = $v;
208
        }
209
210 137
        $this->path = $this->fixPath($parts['path'] ?? '');
211
212 137
        if ($this->isStandardPort()) {
213 99
            $this->port = null;
214
        }
215 137
    }
216
217 138
    private function fixPath(string $path): string
218
    {
219 138
        if (empty($path)) {
220 79
            return $path;
221
        }
222
223
        // Percent encode the path
224 65
        $path = explode('/', $path);
225 65
        foreach ($path as $k => $part) {
226 65
            $path[$k] = false !== strpos($part, '%') ? $part : rawurlencode($part);
227
        }
228
229
        // TODO remove the entry script from the path?
230 65
        $path = str_replace('/index.php', '', join('/', $path));
231
232 65
        return $path;
233
    }
234
235 46
    private function reduceSlashes(string $path): string
236
    {
237 46
        if ('/' === ($path[0] ?? '') && 0 === strlen($this->user)) {
238 19
            return preg_replace('/\/+/', '/', $path);
239
        }
240
241 30
        return $path;
242
    }
243
244 29
    private function getHostWithPort(): string
245
    {
246 29
        if ($this->port) {
247 12
            return $this->host . ($this->isStandardPort() ? '' : ':' . $this->port);
248
        }
249
250 19
        return $this->host;
251
    }
252
253 145
    private function isStandardPort(): bool
254
    {
255 145
        return in_array($this->port, [80, 443, 21, 23, 70, 110, 119, 143, 389]);
256
    }
257
}
258