20
20
*/
21
21
class HeaderBag implements \IteratorAggregate, \Countable, \Stringable
22
22
{
23
+ public const DEFAULT_CACHE_CONTROL_TARGET = '' ;
23
24
protected const UPPER = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ ' ;
24
25
protected const LOWER = '-abcdefghijklmnopqrstuvwxyz ' ;
25
26
26
27
/**
27
28
* @var array<string, list<string|null>>
28
29
*/
29
30
protected array $ headers = [];
30
- protected array $ cacheControl = [];
31
+
32
+ /**
33
+ * Map of target to cache control instructions.
34
+ *
35
+ * @var array<string, CacheControl>
36
+ */
37
+ protected array $ cacheControls = [];
31
38
32
39
public function __construct (array $ headers = [])
33
40
{
@@ -68,10 +75,21 @@ public function __toString(): string
68
75
public function all (?string $ key = null ): array
69
76
{
70
77
if (null !== $ key ) {
71
- return $ this ->headers [strtr ($ key , self ::UPPER , self ::LOWER )] ?? [];
78
+ $ uniqueKey = strtr ($ key , self ::UPPER , self ::LOWER );
79
+ if (str_ends_with ($ uniqueKey , 'cache-control ' )) {
80
+ return [$ this ->getCacheControl ($ this ->extractCacheControlTarget ($ key ))->getCacheControlHeader ()];
81
+ }
82
+
83
+ return $ this ->headers [$ uniqueKey ] ?? [];
72
84
}
73
85
74
- return $ this ->headers ;
86
+ $ headers = $ this ->headers ;
87
+ // edge case: what if extending class directly changed Cache-Control in the $headers array?
88
+ foreach ($ this ->cacheControls as $ target => $ cacheControl ) {
89
+ $ headers [self ::DEFAULT_CACHE_CONTROL_TARGET === $ target ? 'cache-control ' : $ target .'-cache-control ' ] = [$ cacheControl ->getCacheControlHeader ()];
90
+ }
91
+
92
+ return $ headers ;
75
93
}
76
94
77
95
/**
@@ -89,6 +107,7 @@ public function keys(): array
89
107
*/
90
108
public function replace (array $ headers = []): void
91
109
{
110
+ $ this ->cacheControls = [];
92
111
$ this ->headers = [];
93
112
$ this ->add ($ headers );
94
113
}
@@ -129,27 +148,43 @@ public function get(string $key, ?string $default = null): ?string
129
148
*/
130
149
public function set (string $ key , string |array |null $ values , bool $ replace = true ): void
131
150
{
132
- $ key = strtr ($ key , self ::UPPER , self ::LOWER );
151
+ $ uniqueKey = strtr ($ key , self ::UPPER , self ::LOWER );
152
+
153
+ if (str_ends_with ($ uniqueKey , 'cache-control ' )) {
154
+ $ this ->setCacheControlFromHeader ($ key , $ values , $ replace );
155
+
156
+ return ;
157
+ }
133
158
134
159
if (\is_array ($ values )) {
135
160
$ values = array_values ($ values );
136
161
137
- if (true === $ replace || !isset ($ this ->headers [$ key ])) {
138
- $ this ->headers [$ key ] = $ values ;
162
+ if (true === $ replace || !isset ($ this ->headers [$ uniqueKey ])) {
163
+ $ this ->headers [$ uniqueKey ] = $ values ;
139
164
} else {
140
- $ this ->headers [$ key ] = array_merge ($ this ->headers [$ key ], $ values );
165
+ $ this ->headers [$ uniqueKey ] = array_merge ($ this ->headers [$ uniqueKey ], $ values );
141
166
}
142
167
} else {
143
- if (true === $ replace || !isset ($ this ->headers [$ key ])) {
144
- $ this ->headers [$ key ] = [$ values ];
168
+ if (true === $ replace || !isset ($ this ->headers [$ uniqueKey ])) {
169
+ $ this ->headers [$ uniqueKey ] = [$ values ];
145
170
} else {
146
- $ this ->headers [$ key ][] = $ values ;
171
+ $ this ->headers [$ uniqueKey ][] = $ values ;
147
172
}
148
173
}
174
+ }
149
175
150
- if ('cache-control ' === $ key ) {
151
- $ this ->cacheControl = $ this ->parseCacheControl (implode (', ' , $ this ->headers [$ key ]));
176
+ private function setCacheControlFromHeader (string $ key , string |array |null $ values , bool $ replace = true ): void
177
+ {
178
+ if (is_array ($ values )) {
179
+ $ values = implode (', ' , $ values );
180
+ }
181
+ $ target = $ this ->extractCacheControlTarget ($ key );
182
+ if ($ replace ) {
183
+ $ this ->cacheControls [$ target ] = CacheControl::fromHeader ($ values );
184
+
185
+ return ;
152
186
}
187
+ $ this ->getCacheControl ($ target )->addCacheControlDirectives (CacheControl::parseCacheControl ($ values ));
153
188
}
154
189
155
190
/**
@@ -173,12 +208,12 @@ public function contains(string $key, string $value): bool
173
208
*/
174
209
public function remove (string $ key ): void
175
210
{
176
- $ key = strtr ($ key , self ::UPPER , self ::LOWER );
211
+ $ uniqueKey = strtr ($ key , self ::UPPER , self ::LOWER );
177
212
178
- unset($ this ->headers [$ key ]);
213
+ unset($ this ->headers [$ uniqueKey ]);
179
214
180
- if ('cache-control ' === $ key ) {
181
- $ this ->cacheControl = [] ;
215
+ if (str_ends_with ( $ uniqueKey , 'cache-control ' ) ) {
216
+ $ this ->removeCacheControl ( $ this -> extractCacheControlTarget ( $ key )) ;
182
217
}
183
218
}
184
219
@@ -200,40 +235,65 @@ public function getDate(string $key, ?\DateTimeInterface $default = null): ?\Dat
200
235
return $ date ;
201
236
}
202
237
238
+ /**
239
+ * Get the default or a targeted cache control instruction set.
240
+ *
241
+ * If the set did not exist yet, it is created.
242
+ */
243
+ public function getCacheControl (string $ target = self ::DEFAULT_CACHE_CONTROL_TARGET ): CacheControl
244
+ {
245
+ // TODO do we need to lowercase the targets as well, and track the desired case as we do for the headers in ResponseHeaderBag
246
+ if (! \array_key_exists ($ target , $ this ->cacheControls )) {
247
+ $ this ->cacheControls [$ target ] = new CacheControl ();
248
+ }
249
+
250
+ return $ this ->cacheControls [$ target ];
251
+ }
252
+
253
+ /**
254
+ * Remove cache control settings.
255
+ */
256
+ public function removeCacheControl (string $ target = self ::DEFAULT_CACHE_CONTROL_TARGET ): void
257
+ {
258
+ unset($ this ->cacheControls [$ target ]);
259
+ }
260
+
203
261
/**
204
262
* Adds a custom Cache-Control directive.
263
+ *
264
+ * @deprecated Use getCacheControl()->addCacheControlDirective instead
205
265
*/
206
266
public function addCacheControlDirective (string $ key , bool |string $ value = true ): void
207
267
{
208
- $ this ->cacheControl [$ key ] = $ value ;
209
-
210
- $ this ->set ('Cache-Control ' , $ this ->getCacheControlHeader ());
268
+ $ this ->getCacheControl ()->addCacheControlDirective ($ key , $ value );
211
269
}
212
270
213
271
/**
214
272
* Returns true if the Cache-Control directive is defined.
273
+ *
274
+ * @deprecated Use getCacheControl()->hasCacheControlDirective instead
215
275
*/
216
276
public function hasCacheControlDirective (string $ key ): bool
217
277
{
218
- return \array_key_exists ( $ key , $ this ->cacheControl );
278
+ return $ this ->getCacheControl ()-> hasCacheControlDirective ( $ key );
219
279
}
220
280
221
281
/**
222
282
* Returns a Cache-Control directive value by name.
283
+ *
284
+ * @deprecated Use getCacheControl()->getCacheControlDirective instead
223
285
*/
224
286
public function getCacheControlDirective (string $ key ): bool |string |null
225
287
{
226
- return $ this ->cacheControl [ $ key] ?? null ;
288
+ return $ this ->getCacheControl ()-> getCacheControlDirective ( $ key) ;
227
289
}
228
290
229
291
/**
230
292
* Removes a Cache-Control directive.
231
293
*/
232
294
public function removeCacheControlDirective (string $ key ): void
233
295
{
234
- unset($ this ->cacheControl [$ key ]);
235
-
236
- $ this ->set ('Cache-Control ' , $ this ->getCacheControlHeader ());
296
+ $ this ->getCacheControl ()->removeCacheControlDirective ($ key );
237
297
}
238
298
239
299
/**
@@ -254,20 +314,36 @@ public function count(): int
254
314
return \count ($ this ->headers );
255
315
}
256
316
317
+ /**
318
+ *
319
+ * @deprecated Use getCacheControl()->getCacheControlHeader instead
320
+ */
257
321
protected function getCacheControlHeader (): string
258
322
{
259
- ksort ($ this ->cacheControl );
260
-
261
- return HeaderUtils::toString ($ this ->cacheControl , ', ' );
323
+ return $ this ->getCacheControl ()->getCacheControlHeader ();
262
324
}
263
325
264
326
/**
265
327
* Parses a Cache-Control HTTP header.
328
+ *
329
+ * @deprecated Use CacheControl::fromHeader instead
266
330
*/
267
331
protected function parseCacheControl (string $ header ): array
268
332
{
269
333
$ parts = HeaderUtils::split ($ header , ',= ' );
270
334
271
335
return HeaderUtils::combine ($ parts );
272
336
}
337
+
338
+ /**
339
+ * Get the cache-control target from the header name.
340
+ */
341
+ private function extractCacheControlTarget (string $ headerName ): string
342
+ {
343
+ if ('cache-control ' === strtolower ($ headerName )) {
344
+ return self ::DEFAULT_CACHE_CONTROL_TARGET ;
345
+ }
346
+
347
+ return substr ($ headerName , 0 , -strlen ('-cache-control ' ));
348
+ }
273
349
}
0 commit comments