• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / http / 22837209177

09 Mar 2026 03:32AM UTC coverage: 83.513% (-0.1%) from 83.62%
22837209177

push

github

dg
added CLAUDE.md

932 of 1116 relevant lines covered (83.51%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.18
/src/Http/UrlImmutable.php
1
<?php declare(strict_types=1);
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
namespace Nette\Http;
9

10
use Nette;
11
use function array_slice, explode, http_build_query, implode, ip2long, is_array, is_string, rawurlencode, str_starts_with, strrpos, substr;
12
use const PHP_QUERY_RFC3986;
13

14

15
/**
16
 * Immutable representation of a URL.
17
 *
18
 * <pre>
19
 * scheme  user  password  host  port      path        query    fragment
20
 *   |      |      |        |      |        |            |         |
21
 * /--\   /--\ /------\ /-------\ /--\/------------\ /--------\ /------\
22
 * http://john:x0y17575@nette.org:8042/en/manual.php?name=param#fragment  <-- absoluteUrl
23
 * \______\__________________________/
24
 *     |               |
25
 *  hostUrl        authority
26
 * </pre>
27
 *
28
 * @property-read string $scheme
29
 * @property-read string $user
30
 * @property-read string $password
31
 * @property-read string $host
32
 * @property-read int $port
33
 * @property-read string $path
34
 * @property-read string $query
35
 * @property-read string $fragment
36
 * @property-read string $absoluteUrl
37
 * @property-read string $authority
38
 * @property-read string $hostUrl
39
 * @property-read array<string,mixed> $queryParameters
40
 */
41
class UrlImmutable implements \JsonSerializable
42
{
43
        use Nette\SmartObject;
44

45
        private string $scheme = '';
46
        private string $user = '';
47
        private string $password = '';
48
        private string $host = '';
49
        private ?int $port = null;
50
        private string $path = '';
51

52
        /** @var mixed[] */
53
        private array $query = [];
54
        private string $fragment = '';
55
        private ?string $authority = null;
56

57

58
        /**
59
         * @throws Nette\InvalidArgumentException if URL is malformed
60
         */
61
        public function __construct(string|self|Url $url)
1✔
62
        {
63
                $url = is_string($url) ? new Url($url) : $url;
1✔
64
                [$this->scheme, $this->user, $this->password, $this->host, $this->port, $this->path, $this->query, $this->fragment] = $url->export();
1✔
65
        }
1✔
66

67

68
        public function withScheme(string $scheme): static
1✔
69
        {
70
                $dolly = clone $this;
1✔
71
                $dolly->scheme = $scheme;
1✔
72
                $dolly->authority = null;
1✔
73
                return $dolly;
1✔
74
        }
75

76

77
        public function getScheme(): string
78
        {
79
                return $this->scheme;
1✔
80
        }
81

82

83
        /** @deprecated */
84
        public function withUser(string $user): static
1✔
85
        {
86
                $dolly = clone $this;
1✔
87
                $dolly->user = $user;
1✔
88
                $dolly->authority = null;
1✔
89
                return $dolly;
1✔
90
        }
91

92

93
        /** @deprecated */
94
        public function getUser(): string
95
        {
96
                return $this->user;
1✔
97
        }
98

99

100
        /** @deprecated */
101
        public function withPassword(string $password): static
1✔
102
        {
103
                $dolly = clone $this;
1✔
104
                $dolly->password = $password;
1✔
105
                $dolly->authority = null;
1✔
106
                return $dolly;
1✔
107
        }
108

109

110
        /** @deprecated */
111
        public function getPassword(): string
112
        {
113
                return $this->password;
1✔
114
        }
115

116

117
        /** @deprecated */
118
        public function withoutUserInfo(): static
119
        {
120
                $dolly = clone $this;
1✔
121
                $dolly->user = $dolly->password = '';
1✔
122
                $dolly->authority = null;
1✔
123
                return $dolly;
1✔
124
        }
125

126

127
        public function withHost(string $host): static
1✔
128
        {
129
                $dolly = clone $this;
1✔
130
                $dolly->host = $host;
1✔
131
                $dolly->authority = null;
1✔
132
                return $dolly->setPath($dolly->path);
1✔
133
        }
134

135

136
        public function getHost(): string
137
        {
138
                return $this->host;
1✔
139
        }
140

141

142
        /**
143
         * Returns the specified number of rightmost domain labels (e.g. level 2 of 'www.nette.org' -> 'nette.org').
144
         * Negative values trim from the right instead.
145
         */
146
        public function getDomain(int $level = 2): string
1✔
147
        {
148
                $parts = ip2long($this->host)
1✔
149
                        ? [$this->host]
×
150
                        : explode('.', $this->host);
1✔
151
                $parts = $level >= 0
1✔
152
                        ? array_slice($parts, -$level)
1✔
153
                        : array_slice($parts, 0, $level);
1✔
154
                return implode('.', $parts);
1✔
155
        }
156

157

158
        public function withPort(int $port): static
1✔
159
        {
160
                $dolly = clone $this;
1✔
161
                $dolly->port = $port;
1✔
162
                $dolly->authority = null;
1✔
163
                return $dolly;
1✔
164
        }
165

166

167
        /**
168
         * Returns the port number, falling back to the default port for the scheme if not explicitly set.
169
         */
170
        public function getPort(): ?int
171
        {
172
                return $this->port ?: $this->getDefaultPort();
1✔
173
        }
174

175

176
        /**
177
         * Returns the default port for the current scheme, or null if the scheme is not recognized.
178
         */
179
        public function getDefaultPort(): ?int
180
        {
181
                return Url::$defaultPorts[$this->scheme] ?? null;
1✔
182
        }
183

184

185
        public function withPath(string $path): static
1✔
186
        {
187
                return (clone $this)->setPath($path);
1✔
188
        }
189

190

191
        private function setPath(string $path): static
1✔
192
        {
193
                $this->path = $this->host && !str_starts_with($path, '/') ? '/' . $path : $path;
1✔
194
                return $this;
1✔
195
        }
196

197

198
        public function getPath(): string
199
        {
200
                return $this->path;
1✔
201
        }
202

203

204
        /** @param string|mixed[] $query */
205
        public function withQuery(string|array $query): static
1✔
206
        {
207
                $dolly = clone $this;
1✔
208
                $dolly->query = is_array($query) ? $query : Url::parseQuery($query);
1✔
209
                return $dolly;
1✔
210
        }
211

212

213
        public function getQuery(): string
214
        {
215
                return http_build_query($this->query, '', '&', PHP_QUERY_RFC3986);
1✔
216
        }
217

218

219
        public function withQueryParameter(string $name, mixed $value): static
1✔
220
        {
221
                $dolly = clone $this;
1✔
222
                $dolly->query[$name] = $value;
1✔
223
                return $dolly;
1✔
224
        }
225

226

227
        /** @return mixed[] */
228
        public function getQueryParameters(): array
229
        {
230
                return $this->query;
1✔
231
        }
232

233

234
        /** @return mixed[]|string|null */
235
        public function getQueryParameter(string $name): array|string|null
1✔
236
        {
237
                return $this->query[$name] ?? null;
1✔
238
        }
239

240

241
        public function withFragment(string $fragment): static
1✔
242
        {
243
                $dolly = clone $this;
1✔
244
                $dolly->fragment = $fragment;
1✔
245
                return $dolly;
1✔
246
        }
247

248

249
        public function getFragment(): string
250
        {
251
                return $this->fragment;
1✔
252
        }
253

254

255
        public function getAbsoluteUrl(): string
256
        {
257
                return $this->getHostUrl() . $this->path
1✔
258
                        . (($tmp = $this->getQuery()) ? '?' . $tmp : '')
1✔
259
                        . ($this->fragment === '' ? '' : '#' . $this->fragment);
1✔
260
        }
261

262

263
        /**
264
         * Returns the [user[:pass]@]host[:port] part of URI.
265
         */
266
        public function getAuthority(): string
267
        {
268
                return $this->authority ??= $this->host === ''
1✔
269
                        ? ''
×
270
                        : ($this->user !== ''
1✔
271
                                ? rawurlencode($this->user) . ($this->password === '' ? '' : ':' . rawurlencode($this->password)) . '@'
1✔
272
                                : '')
1✔
273
                        . $this->host
1✔
274
                        . ($this->port && $this->port !== $this->getDefaultPort()
1✔
275
                                ? ':' . $this->port
1✔
276
                                : '');
1✔
277
        }
278

279

280
        /**
281
         * Returns the scheme and authority part of URI.
282
         */
283
        public function getHostUrl(): string
284
        {
285
                return ($this->scheme === '' ? '' : $this->scheme . ':')
1✔
286
                        . ($this->host === '' ? '' : '//' . $this->getAuthority());
1✔
287
        }
288

289

290
        public function __toString(): string
291
        {
292
                return $this->getAbsoluteUrl();
1✔
293
        }
294

295

296
        /**
297
         * Checks whether two URLs are equal, ignoring query parameter order and trailing dots in hostnames.
298
         */
299
        public function isEqual(string|Url|self $url): bool
1✔
300
        {
301
                return (new Url($this))->isEqual($url);
1✔
302
        }
303

304

305
        /**
306
         * Resolves a URI reference against this URL the same way a browser would.
307
         * Relative paths are resolved against the current path; paths starting with / are resolved against the host root.
308
         */
309
        public function resolve(string $reference): self
1✔
310
        {
311
                $ref = new self($reference);
1✔
312
                if ($ref->scheme !== '') {
1✔
313
                        $ref->path = Url::removeDotSegments($ref->path);
1✔
314
                        return $ref;
1✔
315
                }
316

317
                $ref->scheme = $this->scheme;
1✔
318

319
                if ($ref->host !== '') {
1✔
320
                        $ref->path = Url::removeDotSegments($ref->path);
1✔
321
                        return $ref;
1✔
322
                }
323

324
                $ref->host = $this->host;
1✔
325
                $ref->port = $this->port;
1✔
326

327
                if ($ref->path === '') {
1✔
328
                        $ref->path = $this->path;
1✔
329
                        $ref->query = $ref->query ?: $this->query;
1✔
330
                } elseif (str_starts_with($ref->path, '/')) {
1✔
331
                        $ref->path = Url::removeDotSegments($ref->path);
1✔
332
                } else {
333
                        $ref->path = Url::removeDotSegments($this->mergePath($ref->path));
1✔
334
                }
335
                return $ref;
1✔
336
        }
337

338

339
        /** @internal */
340
        protected function mergePath(string $path): string
1✔
341
        {
342
                $pos = strrpos($this->path, '/');
1✔
343
                return $pos === false ? $path : substr($this->path, 0, $pos + 1) . $path;
1✔
344
        }
345

346

347
        public function jsonSerialize(): string
348
        {
349
                return $this->getAbsoluteUrl();
1✔
350
        }
351

352

353
        /** @internal */
354
        final public function export(): array
355
        {
356
                return [$this->scheme, $this->user, $this->password, $this->host, $this->port, $this->path, $this->query, $this->fragment];
1✔
357
        }
358
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc