@@ -81,14 +81,15 @@ public function add(Response $response)
81
81
return ;
82
82
}
83
83
84
+ $ isHeuristicallyCacheable = $ response ->headers ->hasCacheControlDirective ('public ' );
84
85
$ maxAge = $ response ->headers ->hasCacheControlDirective ('max-age ' ) ? (int ) $ response ->headers ->getCacheControlDirective ('max-age ' ) : null ;
85
- $ this ->storeRelativeAgeDirective ('max-age ' , $ maxAge , $ age );
86
+ $ this ->storeRelativeAgeDirective ('max-age ' , $ maxAge , $ age, $ isHeuristicallyCacheable );
86
87
$ sharedMaxAge = $ response ->headers ->hasCacheControlDirective ('s-maxage ' ) ? (int ) $ response ->headers ->getCacheControlDirective ('s-maxage ' ) : $ maxAge ;
87
- $ this ->storeRelativeAgeDirective ('s-maxage ' , $ sharedMaxAge , $ age );
88
+ $ this ->storeRelativeAgeDirective ('s-maxage ' , $ sharedMaxAge , $ age, $ isHeuristicallyCacheable );
88
89
89
90
$ expires = $ response ->getExpires ();
90
91
$ expires = null !== $ expires ? (int ) $ expires ->format ('U ' ) - (int ) $ response ->getDate ()->format ('U ' ) : null ;
91
- $ this ->storeRelativeAgeDirective ('expires ' , $ expires >= 0 ? $ expires : null , 0 );
92
+ $ this ->storeRelativeAgeDirective ('expires ' , $ expires >= 0 ? $ expires : null , 0 , $ isHeuristicallyCacheable );
92
93
}
93
94
94
95
/**
@@ -199,11 +200,29 @@ private function willMakeFinalResponseUncacheable(Response $response): bool
199
200
* we have to subtract the age so that the value is normalized for an age of 0.
200
201
*
201
202
* If the value is lower than the currently stored value, we update the value, to keep a rolling
202
- * minimal value of each instruction. If the value is NULL, the directive will not be set on the final response.
203
+ * minimal value of each instruction.
204
+ *
205
+ * If the value is NULL and the isHeuristicallyCacheable parameter is false, the directive will
206
+ * not be set on the final response. In this case, not all responses had the directive set and no
207
+ * value can be found that satisfies the requirements of all responses. The directive will be dropped
208
+ * from the final response.
209
+ *
210
+ * If the isHeuristicallyCacheable parameter is true, however, the current response has been marked
211
+ * as cacheable in a public (shared) cache, but did not provide an explicit lifetime that would serve
212
+ * as an upper bound. In this case, we can proceed and possibly keep the directive on the final response.
203
213
*/
204
- private function storeRelativeAgeDirective (string $ directive , ?int $ value , int $ age )
214
+ private function storeRelativeAgeDirective (string $ directive , ?int $ value , int $ age, bool $ isHeuristicallyCacheable )
205
215
{
206
216
if (null === $ value ) {
217
+ if ($ isHeuristicallyCacheable ) {
218
+ /*
219
+ * See https://datatracker.ietf.org/doc/html/rfc7234#section-4.2.2
220
+ * This particular response does not require maximum lifetime; heuristics might be applied.
221
+ * Other responses, however, might have more stringent requirements on maximum lifetime.
222
+ * So, return early here so that the final response can have the more limiting value set.
223
+ */
224
+ return ;
225
+ }
207
226
$ this ->ageDirectives [$ directive ] = false ;
208
227
}
209
228
0 commit comments