Uri.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  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 scheme.
  107. */
  108. public function scheme(): ?string
  109. {
  110. return $this->uri->getScheme();
  111. }
  112. /**
  113. * Get the user from the URI.
  114. */
  115. public function user(bool $withPassword = false): ?string
  116. {
  117. return $withPassword
  118. ? $this->uri->getUserInfo()
  119. : $this->uri->getUsername();
  120. }
  121. /**
  122. * Get the password from the URI.
  123. */
  124. public function password(): ?string
  125. {
  126. return $this->uri->getPassword();
  127. }
  128. /**
  129. * Get the URI's host.
  130. */
  131. public function host(): ?string
  132. {
  133. return $this->uri->getHost();
  134. }
  135. /**
  136. * Get the URI's port.
  137. */
  138. public function port(): ?int
  139. {
  140. return $this->uri->getPort();
  141. }
  142. /**
  143. * Get the URI's path.
  144. *
  145. * Empty or missing paths are returned as a single "/".
  146. */
  147. public function path(): ?string
  148. {
  149. $path = trim((string) $this->uri->getPath(), '/');
  150. return $path === '' ? '/' : $path;
  151. }
  152. /**
  153. * Get the URI's path segments.
  154. *
  155. * Empty or missing paths are returned as an empty collection.
  156. */
  157. public function pathSegments(): Collection
  158. {
  159. $path = $this->path();
  160. return $path === '/' ? new Collection : new Collection(explode('/', $path));
  161. }
  162. /**
  163. * Get the URI's query string.
  164. */
  165. public function query(): UriQueryString
  166. {
  167. return new UriQueryString($this);
  168. }
  169. /**
  170. * Get the URI's fragment.
  171. */
  172. public function fragment(): ?string
  173. {
  174. return $this->uri->getFragment();
  175. }
  176. /**
  177. * Specify the scheme of the URI.
  178. */
  179. public function withScheme(Stringable|string $scheme): static
  180. {
  181. return new static($this->uri->withScheme($scheme));
  182. }
  183. /**
  184. * Specify the user and password for the URI.
  185. */
  186. public function withUser(Stringable|string|null $user, #[SensitiveParameter] Stringable|string|null $password = null): static
  187. {
  188. return new static($this->uri->withUserInfo($user, $password));
  189. }
  190. /**
  191. * Specify the host of the URI.
  192. */
  193. public function withHost(Stringable|string $host): static
  194. {
  195. return new static($this->uri->withHost($host));
  196. }
  197. /**
  198. * Specify the port of the URI.
  199. */
  200. public function withPort(?int $port): static
  201. {
  202. return new static($this->uri->withPort($port));
  203. }
  204. /**
  205. * Specify the path of the URI.
  206. */
  207. public function withPath(Stringable|string $path): static
  208. {
  209. return new static($this->uri->withPath(Str::start((string) $path, '/')));
  210. }
  211. /**
  212. * Merge new query parameters into the URI.
  213. */
  214. public function withQuery(array $query, bool $merge = true): static
  215. {
  216. foreach ($query as $key => $value) {
  217. if ($value instanceof UrlRoutable) {
  218. $query[$key] = $value->getRouteKey();
  219. }
  220. }
  221. if ($merge) {
  222. $mergedQuery = $this->query()->all();
  223. foreach ($query as $key => $value) {
  224. data_set($mergedQuery, $key, $value);
  225. }
  226. $newQuery = $mergedQuery;
  227. } else {
  228. $newQuery = [];
  229. foreach ($query as $key => $value) {
  230. data_set($newQuery, $key, $value);
  231. }
  232. }
  233. return new static($this->uri->withQuery(Arr::query($newQuery) ?: null));
  234. }
  235. /**
  236. * Merge new query parameters into the URI if they are not already in the query string.
  237. */
  238. public function withQueryIfMissing(array $query): static
  239. {
  240. $currentQuery = $this->query();
  241. foreach ($query as $key => $value) {
  242. if (! $currentQuery->missing($key)) {
  243. Arr::forget($query, $key);
  244. }
  245. }
  246. return $this->withQuery($query);
  247. }
  248. /**
  249. * Push a value onto the end of a query string parameter that is a list.
  250. */
  251. public function pushOntoQuery(string $key, mixed $value): static
  252. {
  253. $currentValue = data_get($this->query()->all(), $key);
  254. $values = Arr::wrap($value);
  255. return $this->withQuery([$key => match (true) {
  256. is_array($currentValue) && array_is_list($currentValue) => array_values(array_unique([...$currentValue, ...$values])),
  257. is_array($currentValue) => [...$currentValue, ...$values],
  258. ! is_null($currentValue) => [$currentValue, ...$values],
  259. default => $values,
  260. }]);
  261. }
  262. /**
  263. * Remove the given query parameters from the URI.
  264. */
  265. public function withoutQuery(array|string $keys): static
  266. {
  267. return $this->replaceQuery(Arr::except($this->query()->all(), $keys));
  268. }
  269. /**
  270. * Specify new query parameters for the URI.
  271. */
  272. public function replaceQuery(array $query): static
  273. {
  274. return $this->withQuery($query, merge: false);
  275. }
  276. /**
  277. * Specify the fragment of the URI.
  278. */
  279. public function withFragment(string $fragment): static
  280. {
  281. return new static($this->uri->withFragment($fragment));
  282. }
  283. /**
  284. * Create a redirect HTTP response for the given URI.
  285. */
  286. public function redirect(int $status = 302, array $headers = []): RedirectResponse
  287. {
  288. return new RedirectResponse($this->value(), $status, $headers);
  289. }
  290. /**
  291. * Get the URI as a Stringable instance.
  292. *
  293. * @return \Illuminate\Support\Stringable
  294. */
  295. public function toStringable()
  296. {
  297. return Str::of($this->value());
  298. }
  299. /**
  300. * Create an HTTP response that represents the URI object.
  301. *
  302. * @param \Illuminate\Http\Request $request
  303. * @return \Symfony\Component\HttpFoundation\Response
  304. */
  305. public function toResponse($request)
  306. {
  307. return new RedirectResponse($this->value());
  308. }
  309. /**
  310. * Get the URI as a string of HTML.
  311. *
  312. * @return string
  313. */
  314. public function toHtml()
  315. {
  316. return $this->value();
  317. }
  318. /**
  319. * Get the decoded string representation of the URI.
  320. */
  321. public function decode(): string
  322. {
  323. if (empty($this->query()->toArray())) {
  324. return $this->value();
  325. }
  326. return Str::replace(Str::after($this->value(), '?'), $this->query()->decode(), $this->value());
  327. }
  328. /**
  329. * Get the string representation of the URI.
  330. */
  331. public function value(): string
  332. {
  333. return (string) $this;
  334. }
  335. /**
  336. * Determine if the URI is currently an empty string.
  337. */
  338. public function isEmpty(): bool
  339. {
  340. return trim($this->value()) === '';
  341. }
  342. /**
  343. * Dump the string representation of the URI.
  344. *
  345. * @param mixed ...$args
  346. * @return $this
  347. */
  348. public function dump(...$args)
  349. {
  350. dump($this->value(), ...$args);
  351. return $this;
  352. }
  353. /**
  354. * Set the URL generator resolver.
  355. */
  356. public static function setUrlGeneratorResolver(Closure $urlGeneratorResolver): void
  357. {
  358. static::$urlGeneratorResolver = $urlGeneratorResolver;
  359. }
  360. /**
  361. * Get the underlying URI instance.
  362. */
  363. public function getUri(): UriInterface
  364. {
  365. return $this->uri;
  366. }
  367. /**
  368. * Convert the object into a value that is JSON serializable.
  369. *
  370. * @return string
  371. */
  372. public function jsonSerialize(): string
  373. {
  374. return $this->value();
  375. }
  376. /**
  377. * Get the string representation of the URI.
  378. */
  379. public function __toString(): string
  380. {
  381. return $this->uri->toString();
  382. }
  383. }