1 | <?php |
||
2 | declare(strict_types=1); |
||
3 | |||
4 | namespace Thinktomorrow\Url; |
||
5 | |||
6 | use JetBrains\PhpStorm\Pure; |
||
7 | use Thinktomorrow\Url\Exceptions\InvalidUrl; |
||
8 | |||
9 | class Root |
||
10 | { |
||
11 | private ?string $scheme; |
||
12 | private ?string $host; |
||
13 | private ?string $port; |
||
14 | |||
15 | private bool $anonymousScheme; |
||
16 | private ?string $defaultScheme; |
||
17 | private bool $valid; |
||
18 | |||
19 | private function __construct(?string $scheme = null, ?string $host = null, ?string $port = null, bool $anonymousScheme = false, ?string $defaultScheme = 'https://') |
||
20 | { |
||
21 | $this->scheme = $scheme; |
||
22 | $this->host = $host; |
||
23 | $this->port = $port; |
||
24 | $this->anonymousScheme = $anonymousScheme; |
||
25 | $this->defaultScheme = $defaultScheme; |
||
26 | |||
27 | $this->valid = (false !== filter_var($this->get(), FILTER_VALIDATE_URL)); |
||
28 | |||
29 | // if ($this->composeScheme() == 'https://') { |
||
30 | 26 | // $this->secure(); |
|
31 | // } |
||
32 | 26 | } |
|
33 | 26 | ||
34 | 26 | public static function fromString(string $host): self |
|
35 | 26 | { |
|
36 | 26 | return new static(...array_values(static::parse($host))); |
|
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
37 | } |
||
38 | 26 | ||
39 | 22 | #[Pure] |
|
40 | public function get(): string |
||
41 | { |
||
42 | 26 | return $this->composeScheme() . |
|
43 | 16 | $this->host() . |
|
44 | ($this->port() ? ':'.$this->port : null); |
||
45 | 26 | } |
|
46 | |||
47 | 27 | public function valid(): bool |
|
48 | { |
||
49 | 27 | return $this->valid; |
|
50 | } |
||
51 | |||
52 | 26 | public function secure(): self |
|
53 | { |
||
54 | 26 | $this->scheme = 'https'; |
|
55 | 26 | ||
56 | 26 | return $this; |
|
57 | } |
||
58 | |||
59 | 2 | #[Pure] |
|
60 | private function composeScheme(): ?string |
||
61 | 2 | { |
|
62 | return $this->scheme() |
||
63 | ? $this->scheme().'://' |
||
64 | 16 | : ($this->anonymousScheme ? '//' : $this->defaultScheme); |
|
65 | } |
||
66 | 16 | ||
67 | 16 | public function replaceScheme(string $scheme): self |
|
68 | { |
||
69 | 16 | return new static( |
|
70 | $scheme, |
||
71 | $this->host, |
||
72 | 26 | $this->port, |
|
73 | false, |
||
74 | 26 | $this->defaultScheme |
|
75 | ); |
||
76 | } |
||
77 | 7 | ||
78 | public function defaultScheme(?string $defaultScheme = null): self |
||
79 | 7 | { |
|
80 | 7 | return new static( |
|
81 | 7 | $this->scheme, |
|
82 | 7 | $this->host, |
|
83 | 7 | $this->port, |
|
84 | 7 | $this->anonymousScheme, |
|
85 | $defaultScheme |
||
86 | ); |
||
87 | } |
||
88 | 19 | ||
89 | public function host(): ?string |
||
90 | 19 | { |
|
91 | 19 | return $this->host; |
|
92 | 19 | } |
|
93 | 19 | ||
94 | 19 | public function scheme(): ?string |
|
95 | 19 | { |
|
96 | return $this->scheme; |
||
97 | } |
||
98 | |||
99 | 26 | public function port(): ?string |
|
100 | { |
||
101 | 26 | return $this->port; |
|
102 | } |
||
103 | |||
104 | 26 | private static function parse(string $url): array |
|
105 | { |
||
106 | 26 | if (in_array($url, ['//','/'])) { |
|
107 | return [ |
||
108 | 'scheme' => null, |
||
109 | 26 | 'host' => null, |
|
110 | 'port' => null, |
||
111 | 26 | 'anonymousScheme' => false, |
|
112 | ]; |
||
113 | } |
||
114 | 27 | ||
115 | $parsed = parse_url($url); |
||
116 | 27 | ||
117 | if (false === $parsed) { |
||
118 | 3 | throw new InvalidUrl('Failed to parse url. Invalid url ['.$url.'] passed as parameter.'); |
|
119 | } |
||
120 | |||
121 | return [ |
||
122 | 'scheme' => $parsed['scheme'] ?? null, |
||
123 | 'host' => static::parseHost($parsed), |
||
124 | 'port' => isset($parsed['port']) ? (string) $parsed['port'] : null, |
||
125 | 27 | 'anonymousScheme' => static::isAnonymousScheme($url), |
|
126 | ]; |
||
127 | 27 | } |
|
128 | 1 | ||
129 | #[Pure] |
||
130 | public function __toString(): string |
||
131 | { |
||
132 | 26 | return $this->get(); |
|
133 | 26 | } |
|
134 | 26 | ||
135 | 26 | /** |
|
136 | * If an url is passed with anonymous scheme, e.g. //example.com, parse_url will ignore this and |
||
137 | * strip the first tags, so we need to explicitly reassemble the 'anonymous scheme' manually |
||
138 | * |
||
139 | 1 | * @param string $host |
|
140 | * @return bool |
||
141 | 1 | */ |
|
142 | private static function isAnonymousScheme(string $host): bool |
||
143 | { |
||
144 | $parsed = parse_url($host); |
||
145 | |||
146 | return ! isset($parsed['scheme']) && (str_starts_with($host, '//') && isset($parsed['host'])); |
||
147 | } |
||
148 | |||
149 | private static function parseHost(array $parsed): ?string |
||
150 | { |
||
151 | 26 | if (isset($parsed['host'])) { |
|
152 | return $parsed['host']; |
||
153 | 26 | } |
|
154 | |||
155 | 26 | if (! isset($parsed['path'])) { |
|
156 | return null; |
||
157 | } |
||
158 | 26 | ||
159 | // e.g. /foo/bar |
||
160 | 26 | if ((str_starts_with($parsed['path'], '/'))) { |
|
161 | 19 | return null; |
|
162 | } |
||
163 | |||
164 | 20 | // Invalid tld (missing .tld) |
|
165 | if (false == strpos($parsed['path'], '.')) { |
||
0 ignored issues
–
show
|
|||
166 | return null; |
||
167 | 19 | } |
|
168 | |||
169 | // e.g. example.com/foo |
||
170 | 16 | if ((0 < strpos($parsed['path'], '/'))) { |
|
171 | return substr($parsed['path'], 0, strpos($parsed['path'], '/')); |
||
172 | } |
||
173 | 10 | ||
174 | // e.g. foo or example.com |
||
175 | return $parsed['path']; |
||
176 | 10 | } |
|
177 | } |
||
178 |