friendship ended with social-app. php is my new best friend
at main 12 kB view raw
1<?php 2 3declare(strict_types=1); 4 5namespace Fetch\Concerns; 6 7use Fetch\Enum\ContentType; 8use Fetch\Interfaces\ClientHandler; 9use GuzzleHttp\Cookie\CookieJarInterface; 10use InvalidArgumentException; 11 12trait ConfiguresRequests 13{ 14 /** 15 * Set the base URI for the request. 16 * 17 * @param string $baseUri The base URI for requests 18 * @return $this 19 * 20 * @throws InvalidArgumentException If the base URI is invalid 21 */ 22 public function baseUri(string $baseUri): ClientHandler 23 { 24 if (! filter_var($baseUri, \FILTER_VALIDATE_URL)) { 25 throw new InvalidArgumentException("Invalid base URI: {$baseUri}"); 26 } 27 28 $this->options['base_uri'] = rtrim($baseUri, '/'); 29 30 return $this; 31 } 32 33 /** 34 * Set multiple options for the request. 35 * 36 * @param array<string, mixed> $options Request options 37 * @return $this 38 */ 39 public function withOptions(array $options): ClientHandler 40 { 41 $this->options = array_merge($this->options, $options); 42 43 return $this; 44 } 45 46 /** 47 * Set a single option for the request. 48 * 49 * @param string $key Option key 50 * @param mixed $value Option value 51 * @return $this 52 */ 53 public function withOption(string $key, mixed $value): ClientHandler 54 { 55 $this->options[$key] = $value; 56 57 return $this; 58 } 59 60 /** 61 * Set the form parameters for the request. 62 * 63 * @param array<string, mixed> $params Form parameters 64 * @return $this 65 */ 66 public function withFormParams(array $params): ClientHandler 67 { 68 $this->options['form_params'] = $params; 69 70 // Set the content type header for form params 71 if (! $this->hasHeader('Content-Type')) { 72 $this->withHeader('Content-Type', ContentType::FORM_URLENCODED->value); 73 } 74 75 return $this; 76 } 77 78 /** 79 * Set the multipart data for the request. 80 * 81 * @param array<int, array{name: string, contents: mixed, headers?: array<string, string>}> $multipart Multipart data 82 * @return $this 83 */ 84 public function withMultipart(array $multipart): ClientHandler 85 { 86 $this->options['multipart'] = $multipart; 87 88 // Remove any content type headers as they're automatically set by the multipart boundary 89 if ($this->hasHeader('Content-Type')) { 90 unset($this->options['headers']['Content-Type']); 91 } 92 93 return $this; 94 } 95 96 /** 97 * Set the bearer token for the request. 98 * 99 * @param string $token Bearer token 100 * @return $this 101 */ 102 public function withToken(string $token): ClientHandler 103 { 104 $this->withHeader('Authorization', 'Bearer '.$token); 105 106 return $this; 107 } 108 109 /** 110 * Set basic authentication for the request. 111 * 112 * @param string $username Username 113 * @param string $password Password 114 * @return $this 115 */ 116 public function withAuth(string $username, string $password): ClientHandler 117 { 118 $this->options['auth'] = [$username, $password]; 119 120 return $this; 121 } 122 123 /** 124 * Set multiple headers for the request. 125 * 126 * @param array<string, mixed> $headers Headers 127 * @return $this 128 */ 129 public function withHeaders(array $headers): ClientHandler 130 { 131 // Initialize headers array if not set 132 if (! isset($this->options['headers'])) { 133 $this->options['headers'] = []; 134 } 135 136 $this->options['headers'] = array_merge( 137 $this->options['headers'], 138 $headers 139 ); 140 141 return $this; 142 } 143 144 /** 145 * Set a single header for the request. 146 * 147 * @param string $header Header name 148 * @param mixed $value Header value 149 * @return $this 150 */ 151 public function withHeader(string $header, mixed $value): ClientHandler 152 { 153 // Initialize headers array if not set 154 if (! isset($this->options['headers'])) { 155 $this->options['headers'] = []; 156 } 157 158 $this->options['headers'][$header] = $value; 159 160 return $this; 161 } 162 163 /** 164 * Set the request body. 165 * 166 * @param array|string $body Request body 167 * @param string|ContentType $contentType Content type 168 * @return $this 169 */ 170 public function withBody(array|string $body, string|ContentType $contentType = ContentType::JSON): ClientHandler 171 { 172 // Convert string content type to enum if necessary 173 $contentTypeEnum = ContentType::normalizeContentType($contentType); 174 $contentTypeValue = $contentTypeEnum instanceof ContentType 175 ? $contentTypeEnum->value 176 : $contentTypeEnum; 177 178 if (is_array($body)) { 179 if ($contentTypeEnum === ContentType::JSON) { 180 // Use Guzzle's json option for proper JSON handling 181 $this->options['json'] = $body; 182 183 // IMPORTANT: Remove any existing conflicting options to prevent conflicts 184 $this->unsetConflictingOptions(['body', 'form_params', 'multipart']); 185 186 // Set JSON content type header if not already set 187 if (! $this->hasHeader('Content-Type')) { 188 $this->withHeader('Content-Type', ContentType::JSON->value); 189 } 190 } elseif ($contentTypeEnum === ContentType::FORM_URLENCODED) { 191 $this->withFormParams($body); 192 // Ensure no conflicting body option 193 $this->unsetConflictingOptions(['body', 'json', 'multipart']); 194 } elseif ($contentTypeEnum === ContentType::MULTIPART) { 195 $this->withMultipart($body); 196 // Ensure no conflicting body option 197 $this->unsetConflictingOptions(['body', 'json', 'form_params']); 198 } else { 199 // For any other content type, serialize the array to JSON in body 200 $this->options['body'] = json_encode($body); 201 // Remove conflicting options to prevent conflicts 202 $this->unsetConflictingOptions(['json', 'form_params', 'multipart']); 203 if (! $this->hasHeader('Content-Type')) { 204 $this->withHeader('Content-Type', $contentTypeValue); 205 } 206 } 207 } else { 208 // For string bodies, use body option 209 $this->options['body'] = $body; 210 // Remove body-related options to prevent conflicts 211 $this->unsetConflictingOptions(['json', 'form_params', 'multipart']); 212 if (! $this->hasHeader('Content-Type')) { 213 $this->withHeader('Content-Type', $contentTypeValue); 214 } 215 } 216 217 return $this; 218 } 219 220 /** 221 * Set the JSON body for the request. 222 * 223 * @param array<string, mixed> $data JSON data 224 * @param int $options JSON encoding options 225 * @return $this 226 */ 227 public function withJson(array $data, int $options = 0): ClientHandler 228 { 229 // Use Guzzle's built-in json option for proper handling 230 $this->options['json'] = $data; 231 232 // Set JSON content type if not already set 233 if (! $this->hasHeader('Content-Type')) { 234 $this->withHeader('Content-Type', ContentType::JSON->value); 235 } 236 237 return $this; 238 } 239 240 /** 241 * Set multiple query parameters for the request. 242 * 243 * @param array<string, mixed> $queryParams Query parameters 244 * @return $this 245 */ 246 public function withQueryParameters(array $queryParams): ClientHandler 247 { 248 // Initialize query array if not set 249 if (! isset($this->options['query'])) { 250 $this->options['query'] = []; 251 } 252 253 $this->options['query'] = array_merge( 254 $this->options['query'], 255 $queryParams 256 ); 257 258 return $this; 259 } 260 261 /** 262 * Set a single query parameter for the request. 263 * 264 * @param string $name Parameter name 265 * @param mixed $value Parameter value 266 * @return $this 267 */ 268 public function withQueryParameter(string $name, mixed $value): ClientHandler 269 { 270 // Initialize query array if not set 271 if (! isset($this->options['query'])) { 272 $this->options['query'] = []; 273 } 274 275 $this->options['query'][$name] = $value; 276 277 return $this; 278 } 279 280 /** 281 * Set the timeout for the request. 282 * 283 * @param int $seconds Timeout in seconds 284 * @return $this 285 */ 286 public function timeout(int $seconds): ClientHandler 287 { 288 $this->timeout = $seconds; 289 $this->options['timeout'] = $seconds; 290 291 return $this; 292 } 293 294 /** 295 * Set the proxy for the request. 296 * 297 * @param string|array $proxy Proxy configuration 298 * @return $this 299 */ 300 public function withProxy(string|array $proxy): ClientHandler 301 { 302 $this->options['proxy'] = $proxy; 303 304 return $this; 305 } 306 307 /** 308 * Set the cookies for the request. 309 * 310 * @param bool|CookieJarInterface $cookies Cookie jar or boolean 311 * @return $this 312 */ 313 public function withCookies(bool|CookieJarInterface $cookies): ClientHandler 314 { 315 $this->options['cookies'] = $cookies; 316 317 return $this; 318 } 319 320 /** 321 * Set whether to follow redirects. 322 * 323 * @param bool|array $redirects Redirect configuration 324 * @return $this 325 */ 326 public function withRedirects(bool|array $redirects = true): ClientHandler 327 { 328 $this->options['allow_redirects'] = $redirects; 329 330 return $this; 331 } 332 333 /** 334 * Set the certificate for the request. 335 * 336 * @param string|array $cert Certificate path or array 337 * @return $this 338 */ 339 public function withCert(string|array $cert): ClientHandler 340 { 341 $this->options['cert'] = $cert; 342 343 return $this; 344 } 345 346 /** 347 * Set the SSL key for the request. 348 * 349 * @param string|array $sslKey SSL key configuration 350 * @return $this 351 */ 352 public function withSslKey(string|array $sslKey): ClientHandler 353 { 354 $this->options['ssl_key'] = $sslKey; 355 356 return $this; 357 } 358 359 /** 360 * Set the stream option for the request. 361 * 362 * @param bool $stream Whether to stream the response 363 * @return $this 364 */ 365 public function withStream(bool $stream): ClientHandler 366 { 367 $this->options['stream'] = $stream; 368 369 return $this; 370 } 371 372 /** 373 * Reset the handler state. 374 * 375 * @return $this 376 */ 377 public function reset(): ClientHandler 378 { 379 $this->options = []; 380 $this->timeout = null; 381 $this->maxRetries = null; 382 $this->retryDelay = null; 383 $this->isAsync = false; 384 385 return $this; 386 } 387 388 /** 389 * Configure the request body for POST/PUT/PATCH/DELETE requests. 390 * 391 * @param mixed $body The request body 392 * @param string|ContentType $contentType The content type of the request 393 */ 394 protected function configureRequestBody(mixed $body = null, string|ContentType $contentType = ContentType::JSON): void 395 { 396 if (is_null($body)) { 397 return; 398 } 399 400 // Normalize content type 401 $contentTypeEnum = ContentType::normalizeContentType($contentType); 402 403 if (is_array($body)) { 404 match ($contentTypeEnum) { 405 ContentType::JSON => $this->withJson($body), 406 ContentType::FORM_URLENCODED => $this->withFormParams($body), 407 ContentType::MULTIPART => $this->withMultipart($body), 408 default => $this->withBody($body, $contentType) 409 }; 410 411 return; 412 } 413 414 $this->withBody($body, $contentType); 415 } 416 417 /** 418 * Remove conflicting options from the request options array. 419 * 420 * @param array<string> $keys The keys to unset from options 421 */ 422 protected function unsetConflictingOptions(array $keys): void 423 { 424 foreach ($keys as $key) { 425 unset($this->options[$key]); 426 } 427 } 428}