@@ -18,31 +18,25 @@ trait HandlesUris
1818 */
1919 protected function buildFullUri (string $ uri ): string
2020 {
21+ // Get base URI and query parameters from options
2122 $ baseUri = $ this ->options ['base_uri ' ] ?? '' ;
2223 $ queryParams = $ this ->options ['query ' ] ?? [];
2324
24- // Early return if URI is empty
25- if (empty ($ uri ) && empty ($ baseUri )) {
26- throw new InvalidArgumentException ('URI cannot be empty ' );
25+ // Normalize URIs before processing
26+ $ uri = $ this ->normalizeUri ($ uri );
27+ if (! empty ($ baseUri )) {
28+ $ baseUri = $ this ->normalizeUri ($ baseUri );
2729 }
2830
29- // Handle absolute URLs
30- if ($ this ->isAbsoluteUrl ($ uri )) {
31- return $ this ->appendQueryParameters ($ uri , $ queryParams );
32- }
31+ // Validate inputs
32+ $ this ->validateUriInputs ($ uri , $ baseUri );
3333
34- // For relative URLs, require a base URI
35- if (empty ($ baseUri )) {
36- throw new InvalidArgumentException (
37- "Relative URI ' {$ uri }' cannot be used without a base URI. " .
38- 'Set a base URI using the baseUri() method. '
39- );
40- }
34+ // Build the final URI
35+ $ fullUri = $ this ->isAbsoluteUrl ($ uri )
36+ ? $ uri
37+ : $ this ->joinUriPaths ($ baseUri , $ uri );
4138
42- // Handle relative URLs with base URI
43- $ fullUri = $ this ->combineBaseWithRelativeUri ($ baseUri , $ uri );
44-
45- // Append query parameters if they exist
39+ // Add query parameters if any
4640 return $ this ->appendQueryParameters ($ fullUri , $ queryParams );
4741 }
4842
@@ -60,6 +54,35 @@ protected function getFullUri(): string
6054 return $ this ->buildFullUri ($ uri );
6155 }
6256
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+
6386 /**
6487 * Check if a URI is an absolute URL.
6588 *
@@ -72,22 +95,15 @@ protected function isAbsoluteUrl(string $uri): bool
7295 }
7396
7497 /**
75- * Combine a base URI with a relative URI .
98+ * Join base URI with a path properly .
7699 *
77100 * @param string $baseUri The base URI
78- * @param string $relativeUri The relative URI
101+ * @param string $path The path to append
79102 * @return string The combined URI
80103 */
81- protected function combineBaseWithRelativeUri (string $ baseUri , string $ relativeUri ): string
104+ protected function joinUriPaths (string $ baseUri , string $ path ): string
82105 {
83- // Ensure base URI is valid if not empty
84- if (! empty ($ baseUri ) && ! $ this ->isAbsoluteUrl ($ baseUri )) {
85- throw new InvalidArgumentException ("Invalid base URI: {$ baseUri }" );
86- }
87-
88- // Remove trailing slash from base URI and leading slash from relative URI
89- // Then combine them with a forward slash
90- return rtrim ($ baseUri , '/ ' ).'/ ' .ltrim ($ relativeUri , '/ ' );
106+ return rtrim ($ baseUri , '/ ' ).'/ ' .ltrim ($ path , '/ ' );
91107 }
92108
93109 /**
@@ -103,41 +119,66 @@ protected function appendQueryParameters(string $uri, array $queryParams): strin
103119 return $ uri ;
104120 }
105121
106- // Check if the URI has a fragment
107- $ fragments = explode ('# ' , $ uri , 2 );
108- $ baseUri = $ fragments [0 ];
109- $ fragment = isset ($ fragments [1 ]) ? '# ' .$ fragments [1 ] : '' ;
122+ // Split URI to preserve any fragment
123+ [$ baseUri , $ fragment ] = $ this ->splitUriFragment ($ uri );
110124
111- // Parse the URI to determine if it already has query parameters
112- $ parsedUrl = parse_url ($ baseUri );
113-
114- // Determine the separator based on whether the URI already has query parameters
115- $ separator = isset ($ parsedUrl ['query ' ]) && ! empty ($ parsedUrl ['query ' ]) ? '& ' : '? ' ;
125+ // Determine the separator based on URI structure
126+ $ separator = $ this ->getQuerySeparator ($ baseUri );
116127
117128 // Build the query string
118129 $ queryString = http_build_query ($ queryParams );
119130
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+ {
120158 // Handle special case: URI already ends with a question mark
121- if (str_ends_with ($ baseUri , '? ' )) {
122- return $ baseUri . $ queryString . $ fragment ;
159+ if (str_ends_with ($ uri , '? ' )) {
160+ return '' ;
123161 }
124162
125- // Append the query string to the URI before any fragment
126- return $ baseUri .$ separator .$ queryString .$ fragment ;
163+ // Check if the URI already has query parameters
164+ $ parsedUrl = parse_url ($ uri );
165+
166+ return isset ($ parsedUrl ['query ' ]) && ! empty ($ parsedUrl ['query ' ]) ? '& ' : '? ' ;
127167 }
128168
129169 /**
130- * Normalize a URI by ensuring it has the correct format .
170+ * Normalize a URI by removing redundant slashes .
131171 *
132172 * @param string $uri The URI to normalize
133173 * @return string The normalized URI
134174 */
135175 protected function normalizeUri (string $ uri ): string
136176 {
137- // Remove multiple consecutive slashes (except in the scheme )
177+ // Extract scheme if present (e.g., http:// )
138178 if (preg_match ('~^(https?://)~i ' , $ uri , $ matches )) {
139179 $ scheme = $ matches [1 ];
140180 $ rest = substr ($ uri , strlen ($ scheme ));
181+ // Normalize consecutive slashes in the path
141182 $ rest = preg_replace ('~//+~ ' , '/ ' , $ rest );
142183
143184 return $ scheme .$ rest ;
0 commit comments