| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 3 declare (strict_types=1); 4 namespace WordPress\AiClient\Providers\Http\DTO; 5 6 use JsonException; 7 use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; 8 use WordPress\AiClient\Common\AbstractDataTransferObject; 9 use WordPress\AiClient\Common\Exception\InvalidArgumentException; 10 use WordPress\AiClient\Providers\Http\Collections\HeadersCollection; 11 use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum; 12 /** 13 * Represents an HTTP request. 14 * 15 * This class encapsulates HTTP request data that can be converted 16 * to PSR-7 requests by the HTTP transporter. 17 * 18 * @since 0.1.0 19 * 20 * @phpstan-import-type RequestOptionsArrayShape from RequestOptions 21 * @phpstan-type RequestArrayShape array{ 22 * method: string, 23 * uri: string, 24 * headers: array<string, list<string>>, 25 * body?: string|null, 26 * options?: RequestOptionsArrayShape 27 * } 28 * 29 * @extends AbstractDataTransferObject<RequestArrayShape> 30 */ 31 class Request extends AbstractDataTransferObject 32 { 33 public const KEY_METHOD = 'method'; 34 public const KEY_URI = 'uri'; 35 public const KEY_HEADERS = 'headers'; 36 public const KEY_BODY = 'body'; 37 public const KEY_OPTIONS = 'options'; 38 /** 39 * @var HttpMethodEnum The HTTP method. 40 */ 41 protected HttpMethodEnum $method; 42 /** 43 * @var string The request URI. 44 */ 45 protected string $uri; 46 /** 47 * @var HeadersCollection The request headers. 48 */ 49 protected HeadersCollection $headers; 50 /** 51 * @var array<string, mixed>|null The request data (for query params or form data). 52 */ 53 protected ?array $data = null; 54 /** 55 * @var string|null The request body (raw string content). 56 */ 57 protected ?string $body = null; 58 /** 59 * @var RequestOptions|null Request transport options. 60 */ 61 protected ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options = null; 62 /** 63 * Constructor. 64 * 65 * @since 0.1.0 66 * 67 * @param HttpMethodEnum $method The HTTP method. 68 * @param string $uri The request URI. 69 * @param array<string, string|list<string>> $headers The request headers. 70 * @param string|array<string, mixed>|null $data The request data. 71 * @param RequestOptions|null $options The request transport options. 72 * 73 * @throws InvalidArgumentException If the URI is empty. 74 */ 75 public function __construct(HttpMethodEnum $method, string $uri, array $headers = [], $data = null, ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options = null) 76 { 77 if (empty($uri)) { 78 throw new InvalidArgumentException('URI cannot be empty.'); 79 } 80 $this->method = $method; 81 $this->uri = $uri; 82 $this->headers = new HeadersCollection($headers); 83 // Separate data and body based on type 84 if (is_string($data)) { 85 $this->body = $data; 86 } elseif (is_array($data)) { 87 $this->data = $data; 88 } 89 $this->options = $options; 90 } 91 /** 92 * Creates a deep clone of this request. 93 * 94 * Clones the headers collection and request options to ensure 95 * the cloned request is independent of the original. 96 * The HTTP method enum is immutable and can be safely shared. 97 * 98 * @since 0.4.2 99 */ 100 public function __clone() 101 { 102 // Clone headers collection 103 $this->headers = clone $this->headers; 104 // Clone request options if present (contains only primitives) 105 if ($this->options !== null) { 106 $this->options = clone $this->options; 107 } 108 // Note: $method is an immutable enum and can be safely shared 109 } 110 /** 111 * Gets the HTTP method. 112 * 113 * @since 0.1.0 114 * 115 * @return HttpMethodEnum The HTTP method. 116 */ 117 public function getMethod(): HttpMethodEnum 118 { 119 return $this->method; 120 } 121 /** 122 * Gets the request URI. 123 * 124 * For GET requests with array data, appends the data as query parameters. 125 * 126 * @since 0.1.0 127 * 128 * @return string The URI. 129 */ 130 public function getUri(): string 131 { 132 // If GET request with data, append as query parameters 133 if ($this->method === HttpMethodEnum::GET() && $this->data !== null && !empty($this->data)) { 134 $separator = str_contains($this->uri, '?') ? '&' : '?'; 135 return $this->uri . $separator . http_build_query($this->data); 136 } 137 return $this->uri; 138 } 139 /** 140 * Gets the request headers. 141 * 142 * @since 0.1.0 143 * 144 * @return array<string, list<string>> The headers. 145 */ 146 public function getHeaders(): array 147 { 148 return $this->headers->getAll(); 149 } 150 /** 151 * Gets a specific header value. 152 * 153 * @since 0.1.0 154 * 155 * @param string $name The header name (case-insensitive). 156 * @return list<string>|null The header value(s) or null if not found. 157 */ 158 public function getHeader(string $name): ?array 159 { 160 return $this->headers->get($name); 161 } 162 /** 163 * Gets header values as a comma-separated string. 164 * 165 * @since 0.1.0 166 * 167 * @param string $name The header name (case-insensitive). 168 * @return string|null The header values as a comma-separated string, or null if not found. 169 */ 170 public function getHeaderAsString(string $name): ?string 171 { 172 return $this->headers->getAsString($name); 173 } 174 /** 175 * Checks if a header exists. 176 * 177 * @since 0.1.0 178 * 179 * @param string $name The header name (case-insensitive). 180 * @return bool True if the header exists, false otherwise. 181 */ 182 public function hasHeader(string $name): bool 183 { 184 return $this->headers->has($name); 185 } 186 /** 187 * Gets the request body. 188 * 189 * For GET requests, returns null. 190 * For POST/PUT/PATCH requests: 191 * - If body is set, returns it as-is 192 * - If data is set and Content-Type is JSON, returns JSON-encoded data 193 * - If data is set and Content-Type is form, returns URL-encoded data 194 * 195 * @since 0.1.0 196 * 197 * @return string|null The body. 198 * @throws JsonException If the data cannot be encoded to JSON. 199 */ 200 public function getBody(): ?string 201 { 202 // GET requests don't have a body 203 if (!$this->method->hasBody()) { 204 return null; 205 } 206 // If body is set, return it as-is 207 if ($this->body !== null) { 208 return $this->body; 209 } 210 // If data is set, encode based on content type 211 if ($this->data !== null) { 212 $contentType = $this->getContentType(); 213 // JSON encoding 214 if ($contentType !== null && stripos($contentType, 'application/json') !== \false) { 215 return json_encode($this->data, \JSON_THROW_ON_ERROR); 216 } 217 // Default to URL encoding for forms 218 return http_build_query($this->data); 219 } 220 return null; 221 } 222 /** 223 * Gets the Content-Type header value. 224 * 225 * @since 0.1.0 226 * 227 * @return string|null The Content-Type header value or null if not set. 228 */ 229 private function getContentType(): ?string 230 { 231 $values = $this->getHeader('Content-Type'); 232 return $values !== null ? $values[0] : null; 233 } 234 /** 235 * Returns a new instance with the specified header. 236 * 237 * @since 0.1.0 238 * 239 * @param string $name The header name. 240 * @param string|list<string> $value The header value(s). 241 * @return self A new instance with the header. 242 */ 243 public function withHeader(string $name, $value): self 244 { 245 $newHeaders = $this->headers->withHeader($name, $value); 246 $new = clone $this; 247 $new->headers = $newHeaders; 248 return $new; 249 } 250 /** 251 * Returns a new instance with the specified data. 252 * 253 * @since 0.1.0 254 * 255 * @param string|array<string, mixed> $data The request data. 256 * @return self A new instance with the data. 257 */ 258 public function withData($data): self 259 { 260 $new = clone $this; 261 if (is_string($data)) { 262 $new->body = $data; 263 $new->data = null; 264 } elseif (is_array($data)) { 265 $new->data = $data; 266 $new->body = null; 267 } else { 268 $new->data = null; 269 $new->body = null; 270 } 271 return $new; 272 } 273 /** 274 * Gets the request data array. 275 * 276 * @since 0.1.0 277 * 278 * @return array<string, mixed>|null The request data array. 279 */ 280 public function getData(): ?array 281 { 282 return $this->data; 283 } 284 /** 285 * Gets the request options. 286 * 287 * @since 0.2.0 288 * 289 * @return RequestOptions|null Request transport options when configured. 290 */ 291 public function getOptions(): ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions 292 { 293 return $this->options; 294 } 295 /** 296 * Returns a new instance with the specified request options. 297 * 298 * @since 0.2.0 299 * 300 * @param RequestOptions|null $options The request options to apply. 301 * @return self A new instance with the options. 302 */ 303 public function withOptions(?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options): self 304 { 305 $new = clone $this; 306 $new->options = $options; 307 return $new; 308 } 309 /** 310 * {@inheritDoc} 311 * 312 * @since 0.1.0 313 */ 314 public static function getJsonSchema(): array 315 { 316 return ['type' => 'object', 'properties' => [self::KEY_METHOD => ['type' => 'string', 'description' => 'The HTTP method.'], self::KEY_URI => ['type' => 'string', 'description' => 'The request URI.'], self::KEY_HEADERS => ['type' => 'object', 'additionalProperties' => ['type' => 'array', 'items' => ['type' => 'string']], 'description' => 'The request headers.'], self::KEY_BODY => ['type' => ['string'], 'description' => 'The request body.'], self::KEY_OPTIONS => \WordPress\AiClient\Providers\Http\DTO\RequestOptions::getJsonSchema()], 'required' => [self::KEY_METHOD, self::KEY_URI, self::KEY_HEADERS]]; 317 } 318 /** 319 * {@inheritDoc} 320 * 321 * @since 0.1.0 322 * 323 * @return RequestArrayShape 324 */ 325 public function toArray(): array 326 { 327 $array = [ 328 self::KEY_METHOD => $this->method->value, 329 self::KEY_URI => $this->getUri(), 330 // Include query params if GET with data 331 self::KEY_HEADERS => $this->headers->getAll(), 332 ]; 333 // Include body if present (getBody() handles the conversion) 334 $body = $this->getBody(); 335 if ($body !== null) { 336 $array[self::KEY_BODY] = $body; 337 } 338 if ($this->options !== null) { 339 $optionsArray = $this->options->toArray(); 340 if (!empty($optionsArray)) { 341 $array[self::KEY_OPTIONS] = $optionsArray; 342 } 343 } 344 return $array; 345 } 346 /** 347 * {@inheritDoc} 348 * 349 * @since 0.1.0 350 */ 351 public static function fromArray(array $array): self 352 { 353 static::validateFromArrayData($array, [self::KEY_METHOD, self::KEY_URI, self::KEY_HEADERS]); 354 return new self(HttpMethodEnum::from($array[self::KEY_METHOD]), $array[self::KEY_URI], $array[self::KEY_HEADERS] ?? [], $array[self::KEY_BODY] ?? null, isset($array[self::KEY_OPTIONS]) ? \WordPress\AiClient\Providers\Http\DTO\RequestOptions::fromArray($array[self::KEY_OPTIONS]) : null); 355 } 356 /** 357 * Creates a Request instance from a PSR-7 RequestInterface. 358 * 359 * @since 0.2.0 360 * 361 * @param RequestInterface $psrRequest The PSR-7 request to convert. 362 * @return self A new Request instance. 363 * @throws InvalidArgumentException If the HTTP method is not supported. 364 */ 365 public static function fromPsrRequest(RequestInterface $psrRequest): self 366 { 367 $method = HttpMethodEnum::from($psrRequest->getMethod()); 368 $uri = (string) $psrRequest->getUri(); 369 // Convert PSR-7 headers to array format expected by our constructor 370 /** @var array<string, list<string>> $headers */ 371 $headers = $psrRequest->getHeaders(); 372 // Get body content 373 $body = $psrRequest->getBody()->getContents(); 374 $bodyOrData = !empty($body) ? $body : null; 375 return new self($method, $uri, $headers, $bodyOrData); 376 } 377 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sat Jun 13 09:38:55 2026 | Cross-referenced by PHPXref |