Skip to content

Commit 928101e

Browse files
committed
refactor: adds HeadersCollection class
1 parent d7636f7 commit 928101e

File tree

5 files changed

+300
-117
lines changed

5 files changed

+300
-117
lines changed
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WordPress\AiClient\Providers\Http\Collections;
6+
7+
/**
8+
* Simple collection for managing HTTP headers with case-insensitive access.
9+
*
10+
* This class stores HTTP headers while preserving their original casing
11+
* and provides efficient case-insensitive lookups.
12+
*
13+
* @since n.e.x.t
14+
*/
15+
class HeadersCollection
16+
{
17+
/**
18+
* @var array<string, list<string>> The headers with original casing.
19+
*/
20+
private array $headers = [];
21+
22+
/**
23+
* @var array<string, string> Map of lowercase header names to actual header names.
24+
*/
25+
private array $headersMap = [];
26+
27+
/**
28+
* Constructor.
29+
*
30+
* @since n.e.x.t
31+
*
32+
* @param array<string, string|list<string>> $headers Initial headers.
33+
*/
34+
public function __construct(array $headers = [])
35+
{
36+
foreach ($headers as $name => $value) {
37+
$this->set($name, $value);
38+
}
39+
}
40+
41+
/**
42+
* Gets a specific header value.
43+
*
44+
* @since n.e.x.t
45+
*
46+
* @param string $name The header name (case-insensitive).
47+
* @return list<string>|null The header value(s) or null if not found.
48+
*/
49+
public function get(string $name): ?array
50+
{
51+
$lowerName = strtolower($name);
52+
if (!isset($this->headersMap[$lowerName])) {
53+
return null;
54+
}
55+
56+
$actualName = $this->headersMap[$lowerName];
57+
return $this->headers[$actualName];
58+
}
59+
60+
/**
61+
* Gets all headers.
62+
*
63+
* @since n.e.x.t
64+
*
65+
* @return array<string, list<string>> All headers with their original casing.
66+
*/
67+
public function getAll(): array
68+
{
69+
return $this->headers;
70+
}
71+
72+
/**
73+
* Gets the first value of a specific header.
74+
*
75+
* @since n.e.x.t
76+
*
77+
* @param string $name The header name (case-insensitive).
78+
* @return string|null The first header value or null if not found.
79+
*/
80+
public function getAsString(string $name): ?string
81+
{
82+
$values = $this->get($name);
83+
return $values !== null ? $values[0] : null;
84+
}
85+
86+
/**
87+
* Checks if a header exists.
88+
*
89+
* @since n.e.x.t
90+
*
91+
* @param string $name The header name (case-insensitive).
92+
* @return bool True if the header exists, false otherwise.
93+
*/
94+
public function has(string $name): bool
95+
{
96+
return isset($this->headersMap[strtolower($name)]);
97+
}
98+
99+
/**
100+
* Sets a header value, replacing any existing value.
101+
*
102+
* @since n.e.x.t
103+
*
104+
* @param string $name The header name.
105+
* @param string|list<string> $value The header value(s).
106+
* @return void
107+
*/
108+
private function set(string $name, $value): void
109+
{
110+
$normalizedValues = is_array($value) ? array_values($value) : [$value];
111+
$lowerName = strtolower($name);
112+
113+
// If header exists with different casing, use the existing casing
114+
if (isset($this->headersMap[$lowerName])) {
115+
$actualName = $this->headersMap[$lowerName];
116+
$this->headers[$actualName] = $normalizedValues;
117+
} else {
118+
// New header, use provided casing
119+
$this->headers[$name] = $normalizedValues;
120+
$this->headersMap[$lowerName] = $name;
121+
}
122+
}
123+
124+
/**
125+
* Returns a new instance with the specified header.
126+
*
127+
* @since n.e.x.t
128+
*
129+
* @param string $name The header name.
130+
* @param string|list<string> $value The header value(s).
131+
* @return self A new instance with the header.
132+
*/
133+
public function withHeader(string $name, $value): self
134+
{
135+
$new = clone $this;
136+
$new->set($name, $value);
137+
return $new;
138+
}
139+
}

src/Providers/Http/DTO/Request.php

Lines changed: 17 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use InvalidArgumentException;
88
use JsonException;
99
use WordPress\AiClient\Common\AbstractDataTransferObject;
10+
use WordPress\AiClient\Providers\Http\Collections\HeadersCollection;
1011
use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum;
1112

1213
/**
@@ -44,14 +45,9 @@ class Request extends AbstractDataTransferObject
4445
protected string $uri;
4546

4647
/**
47-
* @var array<string, list<string>> The request headers.
48+
* @var HeadersCollection The request headers.
4849
*/
49-
protected array $headers;
50-
51-
/**
52-
* @var array<string, string> Map of lowercase header names to actual header names for fast lookup.
53-
*/
54-
protected array $headersMap;
50+
protected HeadersCollection $headers;
5551

5652
/**
5753
* @var string|array<string, mixed>|null The request data.
@@ -78,8 +74,7 @@ public function __construct(HttpMethodEnum $method, string $uri, array $headers
7874

7975
$this->method = $method;
8076
$this->uri = $uri;
81-
$this->headers = $this->normalizeHeaders($headers);
82-
$this->headersMap = $this->buildHeadersMap($this->headers);
77+
$this->headers = new HeadersCollection($headers);
8378
$this->data = $data;
8479
}
8580

@@ -124,7 +119,7 @@ public function getUri(): string
124119
*/
125120
public function getHeaders(): array
126121
{
127-
return $this->headers;
122+
return $this->headers->getAll();
128123
}
129124

130125
/**
@@ -137,11 +132,7 @@ public function getHeaders(): array
137132
*/
138133
public function getHeader(string $name): ?array
139134
{
140-
$lower = strtolower($name);
141-
if (!isset($this->headersMap[$lower])) {
142-
return null;
143-
}
144-
return $this->headers[$this->headersMap[$lower]];
135+
return $this->headers->get($name);
145136
}
146137

147138
/**
@@ -152,10 +143,9 @@ public function getHeader(string $name): ?array
152143
* @param string $name The header name (case-insensitive).
153144
* @return string|null The first header value or null if not found.
154145
*/
155-
public function getHeaderLine(string $name): ?string
146+
public function getHeaderAsString(string $name): ?string
156147
{
157-
$values = $this->getHeader($name);
158-
return $values !== null ? implode(', ', $values) : null;
148+
return $this->headers->getAsString($name);
159149
}
160150

161151
/**
@@ -168,7 +158,7 @@ public function getHeaderLine(string $name): ?string
168158
*/
169159
public function hasHeader(string $name): bool
170160
{
171-
return isset($this->headersMap[strtolower($name)]);
161+
return $this->headers->has($name);
172162
}
173163

174164
/**
@@ -242,10 +232,10 @@ private function getContentType(): ?string
242232
*/
243233
public function withHeader(string $name, $value): self
244234
{
245-
$headers = $this->headers;
246-
$headers[$name] = is_array($value) ? array_values($value) : [$value];
247-
248-
return new self($this->method, $this->uri, $headers, $this->data);
235+
$newHeaders = $this->headers->withHeader($name, $value);
236+
$new = clone $this;
237+
$new->headers = $newHeaders;
238+
return $new;
249239
}
250240

251241
/**
@@ -258,41 +248,9 @@ public function withHeader(string $name, $value): self
258248
*/
259249
public function withData($data): self
260250
{
261-
return new self($this->method, $this->uri, $this->headers, $data);
262-
}
263-
264-
/**
265-
* Normalizes headers to ensure they are all arrays.
266-
*
267-
* @since n.e.x.t
268-
*
269-
* @param array<string, string|list<string>> $headers The headers to normalize.
270-
* @return array<string, list<string>> The normalized headers.
271-
*/
272-
private function normalizeHeaders(array $headers): array
273-
{
274-
$normalized = [];
275-
foreach ($headers as $name => $value) {
276-
$normalized[$name] = is_array($value) ? array_values($value) : [$value];
277-
}
278-
return $normalized;
279-
}
280-
281-
/**
282-
* Builds a map of lowercase header names to actual header names.
283-
*
284-
* @since n.e.x.t
285-
*
286-
* @param array<string, list<string>> $headers The headers.
287-
* @return array<string, string> The headers map.
288-
*/
289-
private function buildHeadersMap(array $headers): array
290-
{
291-
$map = [];
292-
foreach (array_keys($headers) as $name) {
293-
$map[strtolower($name)] = $name;
294-
}
295-
return $map;
251+
$new = clone $this;
252+
$new->data = $data;
253+
return $new;
296254
}
297255

298256
/**
@@ -354,7 +312,7 @@ public function toArray(): array
354312
$array = [
355313
self::KEY_METHOD => $this->method->value,
356314
self::KEY_URI => $this->uri,
357-
self::KEY_HEADERS => $this->headers,
315+
self::KEY_HEADERS => $this->headers->getAll(),
358316
];
359317

360318
if ($this->data !== null) {

0 commit comments

Comments
 (0)