1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Saxulum\HttpMessage; |
4
|
|
|
|
5
|
|
|
use Psr\Http\Message\UriInterface; |
6
|
|
|
|
7
|
|
|
final class Uri implements UriInterface |
8
|
|
|
{ |
9
|
|
|
/** |
10
|
|
|
* @var string|null |
11
|
|
|
*/ |
12
|
|
|
private $scheme; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* @var string|null |
16
|
|
|
*/ |
17
|
|
|
private $host; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* @var int|null |
21
|
|
|
*/ |
22
|
|
|
private $port; |
23
|
|
|
|
24
|
|
|
const PORT_HTTP = 80; |
25
|
|
|
const PORT_HTTPS = 443; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var string|null |
29
|
|
|
*/ |
30
|
|
|
private $user; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var string|null |
34
|
|
|
*/ |
35
|
|
|
private $password; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var string|null |
39
|
|
|
*/ |
40
|
|
|
private $path; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var string|null |
44
|
|
|
*/ |
45
|
|
|
private $query; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @var string|null |
49
|
|
|
*/ |
50
|
|
|
private $fragment; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @var UriInterface|null |
54
|
|
|
*/ |
55
|
|
|
private $previous; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @param string|null $scheme |
59
|
|
|
* @param string|null $host |
60
|
|
|
* @param int|null $port |
61
|
|
|
* @param string|null $user |
62
|
|
|
* @param string|null $password |
63
|
|
|
* @param string|null $path |
64
|
|
|
* @param string|null $query |
65
|
|
|
* @param string|null $fragment |
66
|
|
|
* @param UriInterface|null $previous |
67
|
|
|
*/ |
68
|
|
|
public function __construct( |
69
|
|
|
string $scheme = null, |
70
|
|
|
string $host = null, |
71
|
|
|
int $port = null, |
72
|
|
|
string $user = null, |
73
|
|
|
string $password = null, |
74
|
|
|
string $path = null, |
75
|
|
|
string $query = null, |
76
|
|
|
string $fragment = null, |
77
|
|
|
UriInterface $previous = null |
78
|
|
|
) { |
79
|
|
|
$this->scheme = $scheme; |
80
|
|
|
$this->host = $host; |
81
|
|
|
$this->port = $port; |
82
|
|
|
$this->user = $user; |
83
|
|
|
$this->password = $password; |
84
|
|
|
$this->path = $path; |
85
|
|
|
$this->query = $query; |
86
|
|
|
$this->fragment = $fragment; |
87
|
|
|
$this->previous = $previous; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @param string $uri |
92
|
|
|
* @param UriInterface|null $previous |
93
|
|
|
* |
94
|
|
|
* @return UriInterface |
95
|
|
|
* |
96
|
|
|
* @throws \InvalidArgumentException |
97
|
|
|
*/ |
98
|
|
|
public static function create(string $uri, UriInterface $previous = null) |
99
|
|
|
{ |
100
|
|
|
$uriParts = parse_url($uri); |
101
|
|
|
if (false === $uriParts) { |
102
|
|
|
throw new \InvalidArgumentException(sprintf('Invalid uri format for parse_url: %s', $uri)); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
return new self( |
106
|
|
|
$uriParts['scheme'] ?? null, |
107
|
|
|
$uriParts['host'] ?? null, |
108
|
|
|
$uriParts['port'] ?? null, |
109
|
|
|
$uriParts['user'] ?? null, |
110
|
|
|
$uriParts['pass'] ?? null, |
111
|
|
|
$uriParts['path'] ?? null, |
112
|
|
|
$uriParts['query'] ?? null, |
113
|
|
|
$uriParts['fragment'] ?? null, |
114
|
|
|
$previous |
115
|
|
|
); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* {@inheritdoc} |
120
|
|
|
*/ |
121
|
|
|
public function getScheme(): string |
122
|
|
|
{ |
123
|
|
|
if (null === $this->scheme) { |
124
|
|
|
return ''; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
return strtolower($this->scheme); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* {@inheritdoc} |
132
|
|
|
*/ |
133
|
|
|
public function getAuthority(): string |
134
|
|
|
{ |
135
|
|
|
if ('' === $host = $this->getHost()) { |
136
|
|
|
return ''; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
$authority = ''; |
140
|
|
|
|
141
|
|
|
if ('' !== $userInfo = $this->getUserInfo()) { |
142
|
|
|
$authority .= $userInfo.'@'; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
$authority .= $host; |
146
|
|
|
|
147
|
|
|
if (null !== $port = $this->getPort()) { |
148
|
|
|
$authority .= ':'.$port; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
return $authority; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* {@inheritdoc} |
156
|
|
|
*/ |
157
|
|
|
public function getUserInfo(): string |
158
|
|
|
{ |
159
|
|
|
if (null === $this->user) { |
160
|
|
|
return ''; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
if (null === $this->password) { |
164
|
|
|
return $this->user; |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
return $this->user.':'.$this->password; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* {@inheritdoc} |
172
|
|
|
*/ |
173
|
|
|
public function getHost(): string |
174
|
|
|
{ |
175
|
|
|
if (null === $this->host) { |
176
|
|
|
return ''; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
return strtolower($this->host); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* {@inheritdoc} |
184
|
|
|
*/ |
185
|
|
|
public function getPort() |
186
|
|
|
{ |
187
|
|
|
if (null === $this->port) { |
188
|
|
|
return null; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
if ($this->port === $this->getPortForScheme($this->getScheme())) { |
192
|
|
|
return null; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
return $this->port; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* {@inheritdoc} |
200
|
|
|
*/ |
201
|
|
|
public function getPath(): string |
202
|
|
|
{ |
203
|
|
|
if (null === $this->path) { |
204
|
|
|
return ''; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
return $this->path; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* {@inheritdoc} |
212
|
|
|
*/ |
213
|
|
|
public function getQuery(): string |
214
|
|
|
{ |
215
|
|
|
if (null === $this->query) { |
216
|
|
|
return ''; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
return $this->query; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* {@inheritdoc} |
224
|
|
|
*/ |
225
|
|
|
public function getFragment(): string |
226
|
|
|
{ |
227
|
|
|
if (null === $this->fragment) { |
228
|
|
|
return ''; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
return $this->fragment; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* @return UriInterface|null |
236
|
|
|
*/ |
237
|
|
|
public function getPrevious() |
238
|
|
|
{ |
239
|
|
|
return $this->previous; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* {@inheritdoc} |
244
|
|
|
*/ |
245
|
|
View Code Duplication |
public function withScheme($scheme): self |
|
|
|
|
246
|
|
|
{ |
247
|
|
|
return new self( |
248
|
|
|
$scheme ?? null, |
249
|
|
|
$this->host, |
250
|
|
|
$this->port, |
251
|
|
|
$this->user, |
252
|
|
|
$this->password, |
253
|
|
|
$this->path, |
254
|
|
|
$this->query, |
255
|
|
|
$this->fragment, |
256
|
|
|
$this |
257
|
|
|
); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* {@inheritdoc} |
262
|
|
|
*/ |
263
|
|
|
public function withUserInfo($user, $password = null): self |
264
|
|
|
{ |
265
|
|
|
return new self( |
266
|
|
|
$this->scheme, |
267
|
|
|
$this->host, |
268
|
|
|
$this->port, |
269
|
|
|
$user ?? null, |
270
|
|
|
$password ?? null, |
271
|
|
|
$this->path, |
272
|
|
|
$this->query, |
273
|
|
|
$this->fragment, |
274
|
|
|
$this |
275
|
|
|
); |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* {@inheritdoc} |
280
|
|
|
*/ |
281
|
|
View Code Duplication |
public function withHost($host): self |
|
|
|
|
282
|
|
|
{ |
283
|
|
|
return new self( |
284
|
|
|
$this->scheme, |
285
|
|
|
$host ?? null, |
286
|
|
|
$this->port, |
287
|
|
|
$this->user, |
288
|
|
|
$this->password, |
289
|
|
|
$this->path, |
290
|
|
|
$this->query, |
291
|
|
|
$this->fragment, |
292
|
|
|
$this |
293
|
|
|
); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* {@inheritdoc} |
298
|
|
|
*/ |
299
|
|
View Code Duplication |
public function withPort($port): self |
|
|
|
|
300
|
|
|
{ |
301
|
|
|
return new self( |
302
|
|
|
$this->scheme, |
303
|
|
|
$this->host, |
304
|
|
|
$port ?? null, |
305
|
|
|
$this->user, |
306
|
|
|
$this->password, |
307
|
|
|
$this->path, |
308
|
|
|
$this->query, |
309
|
|
|
$this->fragment, |
310
|
|
|
$this |
311
|
|
|
); |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* {@inheritdoc} |
316
|
|
|
*/ |
317
|
|
View Code Duplication |
public function withPath($path): self |
|
|
|
|
318
|
|
|
{ |
319
|
|
|
return new self( |
320
|
|
|
$this->scheme, |
321
|
|
|
$this->host, |
322
|
|
|
$this->port, |
323
|
|
|
$this->user, |
324
|
|
|
$this->password, |
325
|
|
|
$path ?? null, |
326
|
|
|
$this->query, |
327
|
|
|
$this->fragment, |
328
|
|
|
$this |
329
|
|
|
); |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
/** |
333
|
|
|
* {@inheritdoc} |
334
|
|
|
*/ |
335
|
|
View Code Duplication |
public function withQuery($query): self |
|
|
|
|
336
|
|
|
{ |
337
|
|
|
return new self( |
338
|
|
|
$this->scheme, |
339
|
|
|
$this->host, |
340
|
|
|
$this->port, |
341
|
|
|
$this->user, |
342
|
|
|
$this->password, |
343
|
|
|
$this->path, |
344
|
|
|
$query ?? null, |
345
|
|
|
$this->fragment, |
346
|
|
|
$this |
347
|
|
|
); |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
/** |
351
|
|
|
* {@inheritdoc} |
352
|
|
|
*/ |
353
|
|
View Code Duplication |
public function withFragment($fragment): self |
|
|
|
|
354
|
|
|
{ |
355
|
|
|
return new self( |
356
|
|
|
$this->scheme, |
357
|
|
|
$this->host, |
358
|
|
|
$this->port, |
359
|
|
|
$this->user, |
360
|
|
|
$this->password, |
361
|
|
|
$this->path, |
362
|
|
|
$this->query, |
363
|
|
|
$fragment ?? null, |
364
|
|
|
$this |
365
|
|
|
); |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
/** |
369
|
|
|
* {@inheritdoc} |
370
|
|
|
*/ |
371
|
|
|
public function __toString(): string |
372
|
|
|
{ |
373
|
|
|
$uri = ''; |
374
|
|
|
|
375
|
|
|
if ('' !== $scheme = $this->getScheme()) { |
376
|
|
|
$uri .= $scheme.':'; |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
if ('' !== $authority = $this->getAuthority()) { |
380
|
|
|
$uri .= '//'.$authority; |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
if ('' !== $path = $this->getPath()) { |
384
|
|
|
$uri .= $this->getPathForUri($authority, $path); |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
if ('' !== $query = $this->getQuery()) { |
388
|
|
|
$uri .= '?'.$query; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
if ('' !== $fragment = $this->getFragment()) { |
392
|
|
|
$uri .= '#'.$fragment; |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
return $uri; |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
/** |
399
|
|
|
* @param string $scheme |
400
|
|
|
* |
401
|
|
|
* @return int|null |
402
|
|
|
*/ |
403
|
|
|
private function getPortForScheme(string $scheme) |
404
|
|
|
{ |
405
|
|
|
$constantName = 'PORT_'.strtoupper($scheme); |
406
|
|
|
$reflection = new \ReflectionObject($this); |
407
|
|
|
if ($reflection->hasConstant($constantName)) { |
408
|
|
|
return $reflection->getConstant($constantName); |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
return null; |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
/** |
415
|
|
|
* @param string $authority |
416
|
|
|
* @param string $path |
417
|
|
|
* |
418
|
|
|
* @return string |
419
|
|
|
*/ |
420
|
|
|
private function getPathForUri(string $authority, string $path): string |
421
|
|
|
{ |
422
|
|
|
if ('' !== $authority) { |
423
|
|
|
return $this->getPathForUriWithAuthority($path); |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
return $this->getPathForUriWithoutAuthority($path); |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
/** |
430
|
|
|
* @param string $path |
431
|
|
|
* |
432
|
|
|
* @return string |
433
|
|
|
*/ |
434
|
|
|
private function getPathForUriWithAuthority(string $path) |
435
|
|
|
{ |
436
|
|
|
return '/' === $path[0] ? $path : '/'.$path; |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
/** |
440
|
|
|
* @param string $path |
441
|
|
|
* |
442
|
|
|
* @return string |
443
|
|
|
*/ |
444
|
|
|
private function getPathForUriWithoutAuthority(string $path) |
445
|
|
|
{ |
446
|
|
|
$pathLength = strlen($path); |
447
|
|
|
for ($i = 0; $i < $pathLength; ++$i) { |
448
|
|
|
if ('/' !== $path[$i]) { |
449
|
|
|
break; |
450
|
|
|
} |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
return 0 === $i ? $path : '/'.substr($path, $i); |
454
|
|
|
} |
455
|
|
|
} |
456
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.