@@ -211,6 +211,13 @@ class Response
211
211
511 => 'Network Authentication Required ' , // RFC6585
212
212
];
213
213
214
+ /**
215
+ * @var array<string, bool>
216
+ *
217
+ * Tracks headers already sent in informational responses
218
+ */
219
+ private array $ sentHeaders ;
220
+
214
221
/**
215
222
* @param int $status The HTTP status code (200 "OK" by default)
216
223
*
@@ -332,47 +339,51 @@ public function prepare(Request $request): static
332
339
*/
333
340
public function sendHeaders (/* ?int $statusCode = null */ ): static
334
341
{
335
- if (1 > \func_num_args ()) {
336
- trigger_deprecation ('symfony/http-foundation ' , '6.2 ' , 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead. ' , __METHOD__ );
337
-
338
- $ statusCode = null ;
339
- } else {
340
- $ statusCode = func_get_arg (0 ) ?: null ;
341
- }
342
-
343
342
// headers have already been sent by the developer
344
343
if (headers_sent ()) {
345
344
return $ this ;
346
345
}
347
346
347
+ $ statusCode = \func_num_args () > 0 ? func_get_arg (0 ) : null ;
348
+ $ informationalResponse = $ statusCode >= 100 && $ statusCode < 200 ;
349
+ if ($ informationalResponse && !\function_exists ('headers_send ' )) {
350
+ // skip informational responses if not supported by the SAPI
351
+ return $ this ;
352
+ }
353
+
348
354
// headers
349
355
foreach ($ this ->headers ->allPreserveCaseWithoutCookies () as $ name => $ values ) {
356
+ $ previousValues = $ this ->sentHeaders [$ name ] ?? null ;
357
+ if ($ previousValues === $ values ) {
358
+ // Header already sent in a previous response, it will be automatically copied in this response by PHP
359
+ continue ;
360
+ }
361
+
350
362
$ replace = 0 === strcasecmp ($ name , 'Content-Type ' );
351
- foreach ($ values as $ value ) {
363
+
364
+ $ newValues = null === $ previousValues ? $ values : array_diff ($ values , $ previousValues );
365
+ foreach ($ newValues as $ value ) {
352
366
header ($ name .': ' .$ value , $ replace , $ this ->statusCode );
353
367
}
368
+
369
+ if ($ informationalResponse ) {
370
+ $ this ->sentHeaders [$ name ] = $ values ;
371
+ }
354
372
}
355
373
356
374
// cookies
357
375
foreach ($ this ->headers ->getCookies () as $ cookie ) {
358
376
header ('Set-Cookie: ' .$ cookie , false , $ this ->statusCode );
359
377
}
360
378
361
- if ($ statusCode ) {
362
- if (\function_exists ('headers_send ' )) {
363
- headers_send ($ statusCode );
379
+ if ($ informationalResponse ) {
380
+ headers_send ($ statusCode );
364
381
365
- return $ this ;
366
- }
367
-
368
- if ($ statusCode >= 100 && $ statusCode < 200 ) {
369
- // skip informational responses if not supported by the SAPI
370
- return $ this ;
371
- }
372
- } else {
373
- $ statusCode = $ this ->statusCode ;
382
+ return $ this ;
374
383
}
375
384
385
+ $ statusCode ??= $ this ->statusCode ;
386
+
376
387
// status
377
388
header (sprintf ('HTTP/%s %s %s ' , $ this ->version , $ statusCode , $ this ->statusText ), true , $ statusCode );
378
389
0 commit comments