17
17
*
18
18
* @phpstan-type ResponseArrayShape array{
19
19
* statusCode: int,
20
- * headers: array<string, string| list<string>>,
20
+ * headers: array<string, list<string>>,
21
21
* body?: string|null,
22
22
* reasonPhrase: string
23
23
* }
@@ -37,10 +37,15 @@ class Response extends AbstractDataTransferObject
37
37
protected int $ statusCode ;
38
38
39
39
/**
40
- * @var array<string, string| list<string>> The response headers.
40
+ * @var array<string, list<string>> The response headers.
41
41
*/
42
42
protected array $ headers ;
43
43
44
+ /**
45
+ * @var array<string, string> Map of lowercase header names to actual header names for fast lookup.
46
+ */
47
+ protected array $ headersMap ;
48
+
44
49
/**
45
50
* @var string|null The response body.
46
51
*/
@@ -70,7 +75,8 @@ public function __construct(int $statusCode, array $headers, ?string $body = nul
70
75
}
71
76
72
77
$ this ->statusCode = $ statusCode ;
73
- $ this ->headers = $ headers ;
78
+ $ this ->headers = $ this ->normalizeHeaderValues ($ headers );
79
+ $ this ->headersMap = $ this ->buildHeadersMap ($ this ->headers );
74
80
$ this ->body = $ body ;
75
81
$ this ->reasonPhrase = $ reasonPhrase ;
76
82
}
@@ -92,7 +98,7 @@ public function getStatusCode(): int
92
98
*
93
99
* @since n.e.x.t
94
100
*
95
- * @return array<string, string| list<string>> The headers.
101
+ * @return array<string, list<string>> The headers.
96
102
*/
97
103
public function getHeaders (): array
98
104
{
@@ -105,17 +111,29 @@ public function getHeaders(): array
105
111
* @since n.e.x.t
106
112
*
107
113
* @param string $name The header name (case-insensitive).
108
- * @return string| list<string>|null The header value(s) or null if not found.
114
+ * @return list<string>|null The header value(s) or null if not found.
109
115
*/
110
- public function getHeader (string $ name )
116
+ public function getHeader (string $ name ): ? array
111
117
{
112
- // Case-insensitive header lookup
113
- foreach ($ this ->headers as $ key => $ value ) {
114
- if (strcasecmp ($ key , $ name ) === 0 ) {
115
- return $ value ;
116
- }
118
+ $ lower = strtolower ($ name );
119
+ if (!isset ($ this ->headersMap [$ lower ])) {
120
+ return null ;
117
121
}
118
- return null ;
122
+ return $ this ->headers [$ this ->headersMap [$ lower ]];
123
+ }
124
+
125
+ /**
126
+ * Gets the first value of a specific header.
127
+ *
128
+ * @since n.e.x.t
129
+ *
130
+ * @param string $name The header name (case-insensitive).
131
+ * @return string|null The first header value or null if not found.
132
+ */
133
+ public function getHeaderLine (string $ name ): ?string
134
+ {
135
+ $ values = $ this ->getHeader ($ name );
136
+ return $ values !== null ? implode (', ' , $ values ) : null ;
119
137
}
120
138
121
139
/**
@@ -154,6 +172,40 @@ public function isSuccessful(): bool
154
172
return $ this ->statusCode >= 200 && $ this ->statusCode < 300 ;
155
173
}
156
174
175
+ /**
176
+ * Normalizes header values to ensure they are all arrays.
177
+ *
178
+ * @since n.e.x.t
179
+ *
180
+ * @param array<string, string|list<string>> $headers The headers to normalize.
181
+ * @return array<string, list<string>> The normalized headers.
182
+ */
183
+ private function normalizeHeaderValues (array $ headers ): array
184
+ {
185
+ $ normalized = [];
186
+ foreach ($ headers as $ name => $ value ) {
187
+ $ normalized [$ name ] = is_array ($ value ) ? array_values ($ value ) : [$ value ];
188
+ }
189
+ return $ normalized ;
190
+ }
191
+
192
+ /**
193
+ * Builds a map of lowercase header names to actual header names.
194
+ *
195
+ * @since n.e.x.t
196
+ *
197
+ * @param array<string, list<string>> $headers The headers.
198
+ * @return array<string, string> The headers map.
199
+ */
200
+ private function buildHeadersMap (array $ headers ): array
201
+ {
202
+ $ map = [];
203
+ foreach (array_keys ($ headers ) as $ name ) {
204
+ $ map [strtolower ($ name )] = $ name ;
205
+ }
206
+ return $ map ;
207
+ }
208
+
157
209
/**
158
210
* Gets the response data as an array.
159
211
*
@@ -199,13 +251,8 @@ public static function getJsonSchema(): array
199
251
self ::KEY_HEADERS => [
200
252
'type ' => 'object ' ,
201
253
'additionalProperties ' => [
202
- 'oneOf ' => [
203
- ['type ' => 'string ' ],
204
- [
205
- 'type ' => 'array ' ,
206
- 'items ' => ['type ' => 'string ' ],
207
- ],
208
- ],
254
+ 'type ' => 'array ' ,
255
+ 'items ' => ['type ' => 'string ' ],
209
256
],
210
257
'description ' => 'The response headers. ' ,
211
258
],
0 commit comments