Skip to content

Commit a32f921

Browse files
committed
Merge branch 'develop'
* develop: specify next release update changelog add Url::resolve() keep a reference to the parsed Uri|Url centralize the parsing inside the Url move logic of initially relative path move construction of components to internal named constructors first try to parse with rfc3986 Uri to keep the provided value intact
2 parents 87665f0 + a5a1e70 commit a32f921

11 files changed

Lines changed: 598 additions & 136 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## 5.2.0 - 2026-03-17
4+
5+
### Added
6+
7+
- `Innmind\Url\Url::resolve()`
8+
9+
### Fixed
10+
11+
- Urls made only of relative paths were improperly parsed
12+
313
## 5.1.0 - 2026-01-24
414

515
### Changed

src/Authority/Host.php

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
namespace Innmind\Url\Authority;
55

6+
use Innmind\Url\Url;
67
use Uri\WhatWg\Url as Concrete;
8+
use Uri\Rfc3986\Uri;
79

810
/**
911
* @psalm-immutable
@@ -21,13 +23,9 @@ private function __construct(private string $value)
2123
public static function of(string $value): self
2224
{
2325
try {
24-
/** @psalm-suppress ImpureMethodCall */
25-
$url = new Concrete('http://a.org');
26-
/** @psalm-suppress ImpureMethodCall */
27-
$url = $url->withHost($value);
28-
29-
/** @psalm-suppress ImpureMethodCall */
30-
return new self($value);
26+
return Url::of('http://'.$value)
27+
->authority()
28+
->host();
3129
} catch (\Exception) {
3230
throw new \DomainException($value);
3331
}
@@ -42,6 +40,47 @@ public static function none(): self
4240
return new self('');
4341
}
4442

43+
/**
44+
* @internal
45+
* @psalm-pure
46+
*/
47+
public static function parsed(Uri $parsed): self
48+
{
49+
/** @psalm-suppress ImpureMethodCall */
50+
$host = $parsed->getRawHost();
51+
52+
return match ($host) {
53+
null => self::none(),
54+
default => new self($host),
55+
};
56+
}
57+
58+
/**
59+
* @internal
60+
* @psalm-pure
61+
*/
62+
public static function parsedUrl(
63+
Concrete $parsed,
64+
#[\SensitiveParameter] string $origin,
65+
): self {
66+
/** @psalm-suppress ImpureMethodCall */
67+
$host = $parsed->getUnicodeHost();
68+
69+
if (!\is_null($host) && !\str_contains($origin, $host)) {
70+
/** @psalm-suppress ImpureMethodCall */
71+
$host = $parsed->getAsciiHost();
72+
}
73+
74+
if ($host === 'a.org' && !\str_contains($origin, 'a.org')) {
75+
return self::none();
76+
}
77+
78+
return match ($host) {
79+
null => self::none(),
80+
default => new self($host),
81+
};
82+
}
83+
4584
#[\NoDiscard]
4685
public function equals(self $host): bool
4786
{

src/Authority/Port.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
namespace Innmind\Url\Authority;
55

6+
use Uri\WhatWg\Url as Concrete;
7+
use Uri\Rfc3986\Uri;
8+
69
/**
710
* @psalm-immutable
811
*/
@@ -34,6 +37,21 @@ public static function none(): self
3437
return new self(null);
3538
}
3639

40+
/**
41+
* @internal
42+
* @psalm-pure
43+
*/
44+
public static function parsed(Uri|Concrete $parsed): self
45+
{
46+
/** @psalm-suppress ImpureMethodCall */
47+
$port = $parsed->getPort();
48+
49+
return match ($port) {
50+
null => self::none(),
51+
default => new self($port),
52+
};
53+
}
54+
3755
#[\NoDiscard]
3856
public function equals(self $port): bool
3957
{

src/Authority/UserInformation/Password.php

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
namespace Innmind\Url\Authority\UserInformation;
55

6+
use Innmind\Url\Url;
67
use Uri\WhatWg\Url as Concrete;
8+
use Uri\Rfc3986\Uri;
79

810
/**
911
* @psalm-immutable
@@ -25,13 +27,13 @@ private function __construct(string $value)
2527
public static function of(#[\SensitiveParameter] string $value): self
2628
{
2729
try {
28-
/** @psalm-suppress ImpureMethodCall */
29-
$url = new Concrete('http://a.org');
30-
/** @psalm-suppress ImpureMethodCall */
31-
$url = $url->withPassword($value);
32-
33-
/** @psalm-suppress ImpureMethodCall */
34-
return new self((string) $url->getPassword());
30+
return Url::of(\sprintf(
31+
'http://u:%s@a.org',
32+
$value,
33+
))
34+
->authority()
35+
->userInformation()
36+
->password();
3537
} catch (\Exception) {
3638
throw new \DomainException($value);
3739
}
@@ -46,6 +48,21 @@ public static function none(): self
4648
return new self('');
4749
}
4850

51+
/**
52+
* @internal
53+
* @psalm-pure
54+
*/
55+
public static function parsed(Uri|Concrete $parsed): self
56+
{
57+
/** @psalm-suppress ImpureMethodCall */
58+
$password = $parsed->getPassword();
59+
60+
return match ($password) {
61+
null => self::none(),
62+
default => new self($password),
63+
};
64+
}
65+
4966
#[\NoDiscard]
5067
public function equals(self $password): bool
5168
{

src/Authority/UserInformation/User.php

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33

44
namespace Innmind\Url\Authority\UserInformation;
55

6-
use Innmind\Url\Authority\Host;
6+
use Innmind\Url\{
7+
Url,
8+
Authority\Host,
9+
};
710
use Uri\WhatWg\Url as Concrete;
11+
use Uri\Rfc3986\Uri;
812

913
/**
1014
* @psalm-immutable
@@ -22,13 +26,13 @@ private function __construct(private string $value)
2226
public static function of(string $value): self
2327
{
2428
try {
25-
/** @psalm-suppress ImpureMethodCall */
26-
$url = new Concrete('http://a.org');
27-
/** @psalm-suppress ImpureMethodCall */
28-
$url = $url->withUsername($value);
29-
30-
/** @psalm-suppress ImpureMethodCall */
31-
return new self((string) $url->getUsername());
29+
return Url::of(\sprintf(
30+
'http://%s@a.org',
31+
$value,
32+
))
33+
->authority()
34+
->userInformation()
35+
->user();
3236
} catch (\Exception) {
3337
throw new \DomainException($value);
3438
}
@@ -43,6 +47,21 @@ public static function none(): self
4347
return new self('');
4448
}
4549

50+
/**
51+
* @internal
52+
* @psalm-pure
53+
*/
54+
public static function parsed(Uri|Concrete $parsed): self
55+
{
56+
/** @psalm-suppress ImpureMethodCall */
57+
$user = $parsed->getUsername();
58+
59+
return match ($user) {
60+
null => self::none(),
61+
default => new self($user),
62+
};
63+
}
64+
4665
#[\NoDiscard]
4766
public function equals(self $user): bool
4867
{

src/Fragment.php

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
namespace Innmind\Url;
55

66
use Uri\WhatWg\Url as Concrete;
7+
use Uri\Rfc3986\Uri;
78

89
/**
910
* @psalm-immutable
@@ -21,13 +22,7 @@ private function __construct(private string $value)
2122
public static function of(string $value): self
2223
{
2324
try {
24-
/** @psalm-suppress ImpureMethodCall */
25-
$url = new Concrete('http://a.org');
26-
/** @psalm-suppress ImpureMethodCall */
27-
$url = $url->withFragment($value);
28-
29-
/** @psalm-suppress ImpureMethodCall */
30-
return new self((string) $url->getFragment());
25+
return Url::of('http://a.org/#'.$value)->fragment();
3126
} catch (\Exception) {
3227
throw new \DomainException($value);
3328
}
@@ -42,6 +37,21 @@ public static function none(): self
4237
return new self('');
4338
}
4439

40+
/**
41+
* @internal
42+
* @psalm-pure
43+
*/
44+
public static function parsed(Uri|Concrete $parsed): self
45+
{
46+
/** @psalm-suppress ImpureMethodCall */
47+
$fragment = $parsed->getFragment();
48+
49+
return match ($fragment) {
50+
null => self::none(),
51+
default => new self($fragment),
52+
};
53+
}
54+
4555
#[\NoDiscard]
4656
public function equals(self $fragment): bool
4757
{

src/Path.php

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
namespace Innmind\Url;
55

66
use Uri\WhatWg\Url as Concrete;
7+
use Uri\Rfc3986\Uri;
78

89
/**
910
* @psalm-immutable
@@ -25,15 +26,16 @@ final public static function of(string $value): self
2526
}
2627

2728
try {
28-
/** @psalm-suppress ImpureMethodCall */
29-
$url = new Concrete('http://a.org/');
30-
/** @psalm-suppress ImpureMethodCall */
31-
$url = $url->withPath($value);
32-
33-
return $value[0] === '/' ? new AbsolutePath($value) : new RelativePath($value);
29+
$path = Url::of($value)->path();
3430
} catch (\Exception) {
3531
throw new \DomainException($value);
3632
}
33+
34+
if ($path instanceof RelativePath) {
35+
return new RelativePath($value);
36+
}
37+
38+
return new AbsolutePath($value);
3739
}
3840

3941
/**
@@ -45,6 +47,45 @@ final public static function none(): self
4547
return new AbsolutePath('');
4648
}
4749

50+
/**
51+
* @internal
52+
* @psalm-pure
53+
*/
54+
final public static function parsed(Uri|Concrete $parsed): self
55+
{
56+
if ($parsed instanceof Uri) {
57+
/** @psalm-suppress ImpureMethodCall */
58+
$path = $parsed->getRawPath();
59+
} else {
60+
/** @psalm-suppress ImpureMethodCall */
61+
$path = $parsed->getPath();
62+
}
63+
64+
if ($path === '') {
65+
return self::none();
66+
}
67+
68+
if ($path[0] === '/') {
69+
return new AbsolutePath($path);
70+
}
71+
72+
return new RelativePath($path);
73+
}
74+
75+
/**
76+
* @internal
77+
* @psalm-pure
78+
*/
79+
final public static function initiallyRelative(self $self): self
80+
{
81+
$path = \substr($self->toString(), 1);
82+
83+
return match ($path) {
84+
'' => self::none(),
85+
default => new RelativePath($path),
86+
};
87+
}
88+
4889
#[\NoDiscard]
4990
final public function equals(self $path): bool
5091
{

src/Query.php

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
namespace Innmind\Url;
55

66
use Uri\WhatWg\Url as Concrete;
7+
use Uri\Rfc3986\Uri;
78

89
/**
910
* @psalm-immutable
@@ -21,13 +22,7 @@ private function __construct(private string $value)
2122
public static function of(string $value): self
2223
{
2324
try {
24-
/** @psalm-suppress ImpureMethodCall */
25-
$url = new Concrete('http://a.org');
26-
/** @psalm-suppress ImpureMethodCall */
27-
$url = $url->withQuery($value);
28-
29-
/** @psalm-suppress ImpureMethodCall */
30-
return new self((string) $url->getQuery());
25+
return Url::of('http://a.org/?'.$value)->query();
3126
} catch (\Exception) {
3227
throw new \DomainException($value);
3328
}
@@ -42,6 +37,21 @@ public static function none(): self
4237
return new self('');
4338
}
4439

40+
/**
41+
* @internal
42+
* @psalm-pure
43+
*/
44+
public static function parsed(Uri|Concrete $parsed): self
45+
{
46+
/** @psalm-suppress ImpureMethodCall */
47+
$query = $parsed->getQuery();
48+
49+
return match ($query) {
50+
null => self::none(),
51+
default => new self($query),
52+
};
53+
}
54+
4555
#[\NoDiscard]
4656
public function equals(self $query): bool
4757
{

0 commit comments

Comments
 (0)