friendship ended with social-app. php is my new best friend
at main 5.6 kB view raw
1<?php 2 3declare(strict_types=1); 4 5namespace Fetch\Concerns; 6 7use InvalidArgumentException; 8 9trait HandlesUris 10{ 11 /** 12 * Build and get the full URI from a base URI and path. 13 * 14 * @param string $uri The path or absolute URI 15 * @return string The full URI 16 * 17 * @throws InvalidArgumentException If the URI is invalid 18 */ 19 protected function buildFullUri(string $uri): string 20 { 21 // Get base URI and query parameters from options 22 $baseUri = $this->options['base_uri'] ?? ''; 23 $queryParams = $this->options['query'] ?? []; 24 25 // Normalize URIs before processing 26 $uri = $this->normalizeUri($uri); 27 if (! empty($baseUri)) { 28 $baseUri = $this->normalizeUri($baseUri); 29 } 30 31 // Validate inputs 32 $this->validateUriInputs($uri, $baseUri); 33 34 // Build the final URI 35 $fullUri = $this->isAbsoluteUrl($uri) 36 ? $uri 37 : $this->joinUriPaths($baseUri, $uri); 38 39 // Add query parameters if any 40 return $this->appendQueryParameters($fullUri, $queryParams); 41 } 42 43 /** 44 * Get the full URI using the URI from options. 45 * 46 * @return string The full URI 47 * 48 * @throws InvalidArgumentException If the URI is invalid 49 */ 50 protected function getFullUri(): string 51 { 52 $uri = $this->options['uri'] ?? ''; 53 54 return $this->buildFullUri($uri); 55 } 56 57 /** 58 * Validate URI and base URI inputs. 59 * 60 * @param string $uri The URI or path 61 * @param string $baseUri The base URI 62 * 63 * @throws InvalidArgumentException If validation fails 64 */ 65 protected function validateUriInputs(string $uri, string $baseUri): void 66 { 67 // Check if we have any URI to work with 68 if (empty($uri) && empty($baseUri)) { 69 throw new InvalidArgumentException('URI cannot be empty'); 70 } 71 72 // For relative URIs, ensure we have a base URI 73 if (! $this->isAbsoluteUrl($uri) && empty($baseUri)) { 74 throw new InvalidArgumentException( 75 "Relative URI '{$uri}' cannot be used without a base URI. ". 76 'Set a base URI using the baseUri() method.' 77 ); 78 } 79 80 // Ensure base URI is valid if provided 81 if (! empty($baseUri) && ! $this->isAbsoluteUrl($baseUri)) { 82 throw new InvalidArgumentException("Invalid base URI: {$baseUri}"); 83 } 84 } 85 86 /** 87 * Check if a URI is an absolute URL. 88 * 89 * @param string $uri The URI to check 90 * @return bool Whether the URI is absolute 91 */ 92 protected function isAbsoluteUrl(string $uri): bool 93 { 94 return filter_var($uri, \FILTER_VALIDATE_URL) !== false; 95 } 96 97 /** 98 * Join base URI with a path properly. 99 * 100 * @param string $baseUri The base URI 101 * @param string $path The path to append 102 * @return string The combined URI 103 */ 104 protected function joinUriPaths(string $baseUri, string $path): string 105 { 106 return rtrim($baseUri, '/').'/'.ltrim($path, '/'); 107 } 108 109 /** 110 * Append query parameters to a URI. 111 * 112 * @param string $uri The URI 113 * @param array<string, mixed> $queryParams The query parameters 114 * @return string The URI with query parameters 115 */ 116 protected function appendQueryParameters(string $uri, array $queryParams): string 117 { 118 if (empty($queryParams)) { 119 return $uri; 120 } 121 122 // Split URI to preserve any fragment 123 [$baseUri, $fragment] = $this->splitUriFragment($uri); 124 125 // Determine the separator based on URI structure 126 $separator = $this->getQuerySeparator($baseUri); 127 128 // Build the query string 129 $queryString = http_build_query($queryParams); 130 131 // Combine everything 132 return $baseUri.$separator.$queryString.$fragment; 133 } 134 135 /** 136 * Split a URI into its base and fragment parts. 137 * 138 * @param string $uri The URI to split 139 * @return array{0: string, 1: string} [baseUri, fragment] 140 */ 141 protected function splitUriFragment(string $uri): array 142 { 143 $fragments = explode('#', $uri, 2); 144 $baseUri = $fragments[0]; 145 $fragment = isset($fragments[1]) ? '#'.$fragments[1] : ''; 146 147 return [$baseUri, $fragment]; 148 } 149 150 /** 151 * Determine the appropriate query string separator for a URI. 152 * 153 * @param string $uri The URI 154 * @return string The separator ('?' or '&' or '') 155 */ 156 protected function getQuerySeparator(string $uri): string 157 { 158 // Handle special case: URI already ends with a question mark 159 if (str_ends_with($uri, '?')) { 160 return ''; 161 } 162 163 // Check if the URI already has query parameters 164 $parsedUrl = parse_url($uri); 165 166 return isset($parsedUrl['query']) && ! empty($parsedUrl['query']) ? '&' : '?'; 167 } 168 169 /** 170 * Normalize a URI by removing redundant slashes. 171 * 172 * @param string $uri The URI to normalize 173 * @return string The normalized URI 174 */ 175 protected function normalizeUri(string $uri): string 176 { 177 // Extract scheme if present (e.g., http://) 178 if (preg_match('~^(https?://)~i', $uri, $matches)) { 179 $scheme = $matches[1]; 180 $rest = substr($uri, strlen($scheme)); 181 // Normalize consecutive slashes in the path 182 $rest = preg_replace('~//+~', '/', $rest); 183 184 return $scheme.$rest; 185 } 186 187 // For non-URLs, just normalize consecutive slashes 188 return preg_replace('~//+~', '/', $uri); 189 } 190}