Uri.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <?php
  2. namespace Illuminate\Support;
  3. use Closure;
  4. use Illuminate\Contracts\Routing\UrlRoutable;
  5. use Illuminate\Contracts\Support\Htmlable;
  6. use Illuminate\Contracts\Support\Responsable;
  7. use Illuminate\Http\RedirectResponse;
  8. use Illuminate\Support\Traits\Conditionable;
  9. use Illuminate\Support\Traits\Dumpable;
  10. use Illuminate\Support\Traits\Macroable;
  11. use Illuminate\Support\Traits\Tappable;
  12. use JsonSerializable;
  13. use League\Uri\Contracts\UriInterface;
  14. use League\Uri\Uri as LeagueUri;
  15. use SensitiveParameter;
  16. use Stringable;
  17. class Uri implements Htmlable, JsonSerializable, Responsable, Stringable
  18. {
  19. use Conditionable, Dumpable, Macroable, Tappable;
  20. /**
  21. * The URI instance.
  22. */
  23. protected UriInterface $uri;
  24. /**
  25. * The URL generator resolver.
  26. */
  27. protected static ?Closure $urlGeneratorResolver = null;
  28. /**
  29. * Create a new parsed URI instance.
  30. */
  31. public function __construct(UriInterface|Stringable|string $uri = '')
  32. {
  33. $this->uri = $uri instanceof UriInterface ? $uri : LeagueUri::new((string) $uri);
  34. }
  35. /**
  36. * Create a new URI instance.
  37. */
  38. public static function of(UriInterface|Stringable|string $uri = ''): static
  39. {
  40. return new static($uri);
  41. }
  42. /**
  43. * Get a URI instance of an absolute URL for the given path.
  44. */
  45. public static function to(string $path): static
  46. {
  47. return new static(call_user_func(static::$urlGeneratorResolver)->to($path));
  48. }
  49. /**
  50. * Get a URI instance for a named route.
  51. *
  52. * @param \BackedEnum|string $name
  53. * @param mixed $parameters
  54. * @param bool $absolute
  55. * @return static
  56. *
  57. * @throws \Symfony\Component\Routing\Exception\RouteNotFoundException|\InvalidArgumentException
  58. */
  59. public static function route($name, $parameters = [], $absolute = true): static
  60. {
  61. return new static(call_user_func(static::$urlGeneratorResolver)->route($name, $parameters, $absolute));
  62. }
  63. /**
  64. * Create a signed route URI instance for a named route.
  65. *
  66. * @param \BackedEnum|string $name
  67. * @param mixed $parameters
  68. * @param \DateTimeInterface|\DateInterval|int|null $expiration
  69. * @param bool $absolute
  70. * @return static
  71. *
  72. * @throws \InvalidArgumentException
  73. */
  74. public static function signedRoute($name, $parameters = [], $expiration = null, $absolute = true): static
  75. {
  76. return new static(call_user_func(static::$urlGeneratorResolver)->signedRoute($name, $parameters, $expiration, $absolute));
  77. }
  78. /**
  79. * Create a temporary signed route URI instance for a named route.
  80. *
  81. * @param \BackedEnum|string $name
  82. * @param \DateTimeInterface|\DateInterval|int $expiration
  83. * @param array $parameters
  84. * @param bool $absolute
  85. * @return static
  86. */
  87. public static function temporarySignedRoute($name, $expiration, $parameters = [], $absolute = true): static
  88. {
  89. return static::signedRoute($name, $parameters, $expiration, $absolute);
  90. }
  91. /**
  92. * Get a URI instance for a controller action.
  93. *
  94. * @param string|array $action
  95. * @param mixed $parameters
  96. * @param bool $absolute
  97. * @return static
  98. *
  99. * @throws \InvalidArgumentException
  100. */
  101. public static function action($action, $parameters = [], $absolute = true): static
  102. {
  103. return new static(call_user_func(static::$urlGeneratorResolver)->action($action, $parameters, $absolute));
  104. }
  105. /**
  106. * Get the URI's authority.
  107. */
  108. public function authority(): ?string
  109. {
  110. return $this->uri->getAuthority();
  111. }
  112. /**
  113. * Get the URI's scheme.
  114. */
  115. public function scheme(): ?string
  116. {
  117. return $this->uri->getScheme();
  118. }
  119. /**
  120. * Get the user from the URI.
  121. */
  122. public function user(bool $withPassword = false): ?string
  123. {
  124. return $withPassword
  125. ? $this->uri->getUserInfo()
  126. : $this->uri->getUsername();
  127. }
  128. /**
  129. * Get the password from the URI.
  130. */
  131. public function password(): ?string
  132. {
  133. return $this->uri->getPassword();
  134. }
  135. /**
  136. * Get the URI's host.
  137. */
  138. public function host(): ?string
  139. {
  140. return $this->uri->getHost();
  141. }
  142. /**
  143. * Get the URI's port.
  144. */
  145. public function port(): ?int
  146. {
  147. return $this->uri->getPort();
  148. }
  149. /**
  150. * Get the URI's path.
  151. *
  152. * Empty or missing paths are returned as a single "/".
  153. *
  154. * @return non-empty-string
  155. */
  156. public function path(): string
  157. {
  158. $path = trim((string) $this->uri->getPath(), '/');
  159. return $path === '' ? '/' : $path;
  160. }
  161. /**
  162. * Get the URI's path segments.
  163. *
  164. * Empty or missing paths are returned as an empty collection.
  165. */
  166. public function pathSegments(): Collection
  167. {
  168. $path = $this->path();
  169. return $path === '/' ? new Collection : new Collection(explode('/', $path));
  170. }
  171. /**
  172. * Get the URI's query string.
  173. */
  174. public function query(): UriQueryString
  175. {
  176. return new UriQueryString($this);
  177. }
  178. /**
  179. * Get the URI's fragment.
  180. */
  181. public function fragment(): ?string
  182. {
  183. return $this->uri->getFragment();
  184. }
  185. /**
  186. * Specify the scheme of the URI.
  187. */
  188. public function withScheme(Stringable|string $scheme): static
  189. {
  190. return new static($this->uri->withScheme($scheme));
  191. }
  192. /**
  193. * Specify the user and password for the URI.
  194. */
  195. public function withUser(Stringable|string|null $user, #[SensitiveParameter] Stringable|string|null $password = null): static
  196. {
  197. return new static($this->uri->withUserInfo($user, $password));
  198. }
  199. /**
  200. * Specify the host of the URI.
  201. */
  202. public function withHost(Stringable|string $host): static
  203. {
  204. return new static($this->uri->withHost($host));
  205. }
  206. /**
  207. * Specify the port of the URI.
  208. */
  209. public function withPort(?int $port): static
  210. {
  211. return new static($this->uri->withPort($port));
  212. }
  213. /**
  214. * Specify the path of the URI.
  215. */
  216. public function withPath(Stringable|string $path): static
  217. {
  218. return new static($this->uri->withPath(Str::start((string) $path, '/')));
  219. }
  220. /**
  221. * Merge new query parameters into the URI.
  222. */
  223. public function withQuery(array $query, bool $merge = true): static
  224. {
  225. foreach ($query as $key => $value) {
  226. if ($value instanceof UrlRoutable) {
  227. $query[$key] = $value->getRouteKey();
  228. }
  229. }
  230. if ($merge) {
  231. $mergedQuery = $this->query()->all();
  232. foreach ($query as $key => $value) {
  233. data_set($mergedQuery, $key, $value);
  234. }
  235. $newQuery = $mergedQuery;
  236. } else {
  237. $newQuery = [];
  238. foreach ($query as $key => $value) {
  239. data_set($newQuery, $key, $value);
  240. }
  241. }
  242. return new static($this->uri->withQuery(Arr::query($newQuery) ?: null));
  243. }
  244. /**
  245. * Merge new query parameters into the URI if they are not already in the query string.
  246. */
  247. public function withQueryIfMissing(array $query): static
  248. {
  249. $currentQuery = $this->query();
  250. foreach ($query as $key => $value) {
  251. if (! $currentQuery->missing($key)) {
  252. Arr::forget($query, $key);
  253. }
  254. }
  255. return $this->withQuery($query);
  256. }
  257. /**
  258. * Push a value onto the end of a query string parameter that is a list.
  259. */
  260. public function pushOntoQuery(string $key, mixed $value): static
  261. {
  262. $currentValue = data_get($this->query()->all(), $key);
  263. $values = Arr::wrap($value);
  264. return $this->withQuery([$key => match (true) {
  265. is_array($currentValue) && array_is_list($currentValue) => array_values(array_unique([...$currentValue, ...$values])),
  266. is_array($currentValue) => [...$currentValue, ...$values],
  267. ! is_null($currentValue) => [$currentValue, ...$values],
  268. default => $values,
  269. }]);
  270. }
  271. /**
  272. * Remove the given query parameters from the URI.
  273. */
  274. public function withoutQuery(array|string $keys): static
  275. {
  276. return $this->replaceQuery(Arr::except($this->query()->all(), $keys));
  277. }
  278. /**
  279. * Specify new query parameters for the URI.
  280. */
  281. public function replaceQuery(array $query): static
  282. {
  283. return $this->withQuery($query, merge: false);
  284. }
  285. /**
  286. * Specify the fragment of the URI.
  287. */
  288. public function withFragment(string $fragment): static
  289. {
  290. return new static($this->uri->withFragment($fragment));
  291. }
  292. /**
  293. * Create a redirect HTTP response for the given URI.
  294. */
  295. public function redirect(int $status = 302, array $headers = []): RedirectResponse
  296. {
  297. return new RedirectResponse($this->value(), $status, $headers);
  298. }
  299. /**
  300. * Get the URI as a Stringable instance.
  301. *
  302. * @return \Illuminate\Support\Stringable
  303. */
  304. public function toStringable()
  305. {
  306. return Str::of($this->value());
  307. }
  308. /**
  309. * Create an HTTP response that represents the URI object.
  310. *
  311. * @param \Illuminate\Http\Request $request
  312. * @return \Symfony\Component\HttpFoundation\Response
  313. */
  314. public function toResponse($request)
  315. {
  316. return new RedirectResponse($this->value());
  317. }
  318. /**
  319. * Get the URI as a string of HTML.
  320. *
  321. * @return string
  322. */
  323. public function toHtml()
  324. {
  325. return $this->value();
  326. }
  327. /**
  328. * Get the decoded string representation of the URI.
  329. */
  330. public function decode(): string
  331. {
  332. if (empty($this->query()->toArray())) {
  333. return $this->value();
  334. }
  335. return Str::replace(Str::after($this->value(), '?'), $this->query()->decode(), $this->value());
  336. }
  337. /**
  338. * Get the string representation of the URI.
  339. */
  340. public function value(): string
  341. {
  342. return (string) $this;
  343. }
  344. /**
  345. * Determine if the URI is currently an empty string.
  346. */
  347. public function isEmpty(): bool
  348. {
  349. return trim($this->value()) === '';
  350. }
  351. /**
  352. * Dump the string representation of the URI.
  353. *
  354. * @param mixed ...$args
  355. * @return $this
  356. */
  357. public function dump(...$args)
  358. {
  359. dump($this->value(), ...$args);
  360. return $this;
  361. }
  362. /**
  363. * Set the URL generator resolver.
  364. */
  365. public static function setUrlGeneratorResolver(Closure $urlGeneratorResolver): void
  366. {
  367. static::$urlGeneratorResolver = $urlGeneratorResolver;
  368. }
  369. /**
  370. * Get the underlying URI instance.
  371. */
  372. public function getUri(): UriInterface
  373. {
  374. return $this->uri;
  375. }
  376. /**
  377. * Convert the object into a value that is JSON serializable.
  378. *
  379. * @return string
  380. */
  381. public function jsonSerialize(): string
  382. {
  383. return $this->value();
  384. }
  385. /**
  386. * Get the string representation of the URI.
  387. */
  388. public function __toString(): string
  389. {
  390. return $this->uri->toString();
  391. }
  392. }