Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
97.89% |
93 / 95 |
|
77.78% |
7 / 9 |
CRAP | |
0.00% |
0 / 1 |
UriFactory | |
97.89% |
93 / 95 |
|
77.78% |
7 / 9 |
49 | |
0.00% |
0 / 1 |
__construct | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
getQuery | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasQuery | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
clean | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
setQuery | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
setupUriBuilder | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
4 | |||
clear | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
clearLike | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
unique | |
100.00% |
37 / 37 |
|
100.00% |
1 / 1 |
26 | |||
build | |
94.12% |
16 / 17 |
|
0.00% |
0 / 1 |
4.00 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package phpOMS\Uri |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace phpOMS\Uri; |
16 | |
17 | /** |
18 | * UriFactory class. |
19 | * |
20 | * Used in order to create a uri |
21 | * |
22 | * @package phpOMS\Uri |
23 | * @license OMS License 2.0 |
24 | * @link https://jingga.app |
25 | * @since 1.0.0 |
26 | */ |
27 | final class UriFactory |
28 | { |
29 | /** |
30 | * Dynamic query elements. |
31 | * |
32 | * @var string[] |
33 | * @since 1.0.0 |
34 | */ |
35 | private static array $uri = []; |
36 | |
37 | /** |
38 | * Function for parsing urls |
39 | * |
40 | * @var null|\Closure |
41 | * @since 1.0.0 |
42 | */ |
43 | private static ?\Closure $replaceFunction = null; |
44 | |
45 | /** |
46 | * Constructor. |
47 | * |
48 | * @since 1.0.0 |
49 | * @codeCoverageIgnore |
50 | */ |
51 | private function __construct() |
52 | { |
53 | } |
54 | |
55 | /** |
56 | * Set global query replacements. |
57 | * |
58 | * @param string $key Replacement key |
59 | * |
60 | * @return null|string |
61 | * |
62 | * @since 1.0.0 |
63 | */ |
64 | public static function getQuery(string $key) : ?string |
65 | { |
66 | return self::$uri[$key] ?? null; |
67 | } |
68 | |
69 | /** |
70 | * Has query replacement. |
71 | * |
72 | * @param string $key Replacement key |
73 | * |
74 | * @return bool |
75 | * |
76 | * @since 1.0.0 |
77 | */ |
78 | public static function hasQuery(string $key) : bool |
79 | { |
80 | return isset(self::$uri[$key]); |
81 | } |
82 | |
83 | /** |
84 | * Cleanup |
85 | * |
86 | * @param string $identifier Identifier for cleaning up (e.g. * = everything, / = only path, ? = only query parameters, # only fragment etc.) |
87 | * |
88 | * @return void |
89 | * |
90 | * @since 1.0.0 |
91 | */ |
92 | public static function clean(string $identifier = '?') : void |
93 | { |
94 | if ($identifier === '*') { |
95 | self::$uri = []; |
96 | } else { |
97 | foreach (self::$uri as $key => $_) { |
98 | if (\str_starts_with($key, $identifier)) { |
99 | unset(self::$uri[$key]); |
100 | } |
101 | } |
102 | } |
103 | } |
104 | |
105 | /** |
106 | * Set global query replacements. |
107 | * |
108 | * @param string $key Replacement key |
109 | * @param string $value Replacement value |
110 | * @param bool $overwrite Overwrite if already exists |
111 | * |
112 | * @return bool |
113 | * |
114 | * @since 1.0.0 |
115 | */ |
116 | public static function setQuery(string $key, string $value, bool $overwrite = false) : bool |
117 | { |
118 | if ($overwrite || !isset(self::$uri[$key])) { |
119 | self::$uri[$key] = $value; |
120 | |
121 | return true; |
122 | } |
123 | |
124 | return false; |
125 | } |
126 | |
127 | /** |
128 | * Setup uri builder based on current request |
129 | * |
130 | * @param UriInterface $uri Uri |
131 | * |
132 | * @return void |
133 | * |
134 | * @since 1.0.0 |
135 | */ |
136 | public static function setupUriBuilder(UriInterface $uri) : void |
137 | { |
138 | self::setQuery('/scheme', $uri->scheme); |
139 | self::setQuery('/host', $uri->host); |
140 | self::setQuery('/port', (string) $uri->port); |
141 | self::setQuery('/tld', \rtrim($uri->getBase(), '/')); |
142 | self::setQuery('/rootPath', $uri->getRootPath()); |
143 | self::setQuery('?', '?' . $uri->getQuery()); |
144 | self::setQuery('%', $uri->__toString()); |
145 | self::setQuery('#', $uri->fragment); |
146 | self::setQuery('/', $uri->getPath()); |
147 | self::setQuery(':user', $uri->user); |
148 | self::setQuery(':pass', $uri->pass); |
149 | |
150 | $data = $uri->getPathElements(); |
151 | foreach ($data as $key => $value) { |
152 | self::setQuery('/' . $key, $value); |
153 | } |
154 | |
155 | $data = $uri->getQueryArray(); |
156 | foreach ($data as $key => $value) { |
157 | self::setQuery('?' . $key, $value, true); |
158 | } |
159 | |
160 | $data = $uri->fragments; |
161 | foreach ($data as $key => $value) { |
162 | self::setQuery('#' . $key, $value, true); |
163 | } |
164 | } |
165 | |
166 | /** |
167 | * Clear uri component |
168 | * |
169 | * @param string $key Uri component key |
170 | * |
171 | * @return bool |
172 | * |
173 | * @since 1.0.0 |
174 | */ |
175 | public static function clear(string $key) : bool |
176 | { |
177 | if (isset(self::$uri[$key])) { |
178 | unset(self::$uri[$key]); |
179 | |
180 | return true; |
181 | } |
182 | |
183 | return false; |
184 | } |
185 | |
186 | /** |
187 | * Clear uri components that follow a certain pattern |
188 | * |
189 | * @param string $pattern Uri key pattern to remove |
190 | * |
191 | * @return bool |
192 | * |
193 | * @since 1.0.0 |
194 | */ |
195 | public static function clearLike(string $pattern) : bool |
196 | { |
197 | $success = false; |
198 | |
199 | foreach (self::$uri as $key => $value) { |
200 | if (((bool) \preg_match('~^' . $pattern . '$~', $key))) { |
201 | unset(self::$uri[$key]); |
202 | $success = true; |
203 | } |
204 | } |
205 | |
206 | return $success; |
207 | } |
208 | |
209 | /** |
210 | * Simplify url |
211 | * |
212 | * While adding, and removing elements to a uri it can have multiple parameters or empty parameters which need to be cleaned up |
213 | * |
214 | * @param string $url Url to simplify |
215 | * |
216 | * @return string |
217 | * |
218 | * @since 1.0.0 |
219 | */ |
220 | private static function unique(string $url) : string |
221 | { |
222 | // handle edge cases / normalization |
223 | /* |
224 | $url = \str_replace( |
225 | ['=%', '=#', '=?'], |
226 | ['=%25', '=%23', '=%3F'], |
227 | $url |
228 | ); |
229 | */ |
230 | |
231 | if (\stripos($url, '?') === false && ($pos = \stripos($url, '&')) !== false) { |
232 | $url = \substr_replace($url, '?', $pos, 1); |
233 | } |
234 | |
235 | /** @var array $urlStructure */ |
236 | $urlStructure = \parse_url($url); |
237 | if ($urlStructure === false) { |
238 | return $url; // @codeCoverageIgnore |
239 | } |
240 | |
241 | if (isset($urlStructure['query'])) { |
242 | $len = \strlen($urlStructure['query']); |
243 | for ($i = 0; $i < $len; ++$i) { |
244 | if ($urlStructure['query'][$i] === '?') { |
245 | $urlStructure['query'] = \substr_replace($urlStructure['query'], '&', $i, 1); |
246 | } elseif ($urlStructure['query'][$i] === '\\') { |
247 | ++$i; |
248 | } |
249 | } |
250 | |
251 | \parse_str($urlStructure['query'], $urlStructure['query']); |
252 | |
253 | foreach ($urlStructure['query'] as $para => $query) { |
254 | if (($query === '' && \stripos($url, $para . '=') !== false) || $query === '---') { |
255 | unset($urlStructure['query'][$para]); |
256 | } |
257 | } |
258 | } |
259 | |
260 | $escaped = |
261 | (isset($urlStructure['scheme']) && !empty($urlStructure['scheme']) |
262 | ? $urlStructure['scheme'] . '://' : '') |
263 | . (isset($urlStructure['username']) |
264 | ? $urlStructure['username'] . ':' : '') |
265 | . (isset($urlStructure['password']) |
266 | ? $urlStructure['password'] . '@' : '') |
267 | . (isset($urlStructure['host']) && !empty($urlStructure['host']) |
268 | ? $urlStructure['host'] : '') |
269 | . (isset($urlStructure['port']) && !empty($urlStructure['port']) |
270 | ? ':' . $urlStructure['port'] : '') |
271 | . (isset($urlStructure['path']) && !empty($urlStructure['path']) |
272 | ? $urlStructure['path'] : '') |
273 | . (isset($urlStructure['query']) && !empty($urlStructure['query']) |
274 | ? '?' . \rtrim(\str_replace('=&', '&', \http_build_query($urlStructure['query'])), '=') : '') |
275 | . (isset($urlStructure['fragment']) && !empty($urlStructure['fragment']) |
276 | ? '#' . \str_replace('\#', '#', $urlStructure['fragment']) : ''); |
277 | |
278 | return \str_replace( |
279 | ['%5C%7B', '%5C%7D', '%5C%3F', '%5C%23'], |
280 | ['{', '}', '?', '#'], |
281 | $escaped |
282 | ); |
283 | } |
284 | |
285 | /** |
286 | * Build uri. |
287 | * |
288 | * # = DOM id |
289 | * . = DOM class |
290 | * / = Current path |
291 | * ? = Current query |
292 | * @ = |
293 | * $ = Other data |
294 | * |
295 | * @param string $uri Path data |
296 | * @param array<string, string> $toMatch Optional special replacements |
297 | * |
298 | * @return string |
299 | * |
300 | * @throws \Exception |
301 | * |
302 | * @since 1.0.0 |
303 | */ |
304 | public static function build(string $uri, array $toMatch = []) : string |
305 | { |
306 | if (\stripos($uri, '{') === false) { |
307 | return $uri; |
308 | } |
309 | |
310 | if (self::$replaceFunction === null) { |
311 | self::$replaceFunction = static function ($match) use ($toMatch) : string { |
312 | $match = \substr($match[0], 1, \strlen($match[0]) - 2); |
313 | |
314 | return (string) ($toMatch[$match] |
315 | ?? (self::$uri[$match] ?? ( |
316 | ($match[0] ?? '') === '?' |
317 | ? '---' // only do this for query parameters |
318 | : '' |
319 | ) |
320 | )); |
321 | }; |
322 | } |
323 | |
324 | $parsed = \preg_replace_callback( |
325 | '(\{[\/#\?%@\.\$][a-zA-Z0-9_\-]*\})', |
326 | self::$replaceFunction, |
327 | $uri |
328 | ); |
329 | |
330 | return self::unique($parsed ?? ''); |
331 | } |
332 | } |