@@ -50,7 +50,7 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface
50
50
private $ ageDirectives = [
51
51
'max-age ' => null ,
52
52
's-maxage ' => null ,
53
- 'expires ' => null ,
53
+ 'expires ' => false ,
54
54
];
55
55
56
56
/**
@@ -81,15 +81,30 @@ public function add(Response $response)
81
81
return ;
82
82
}
83
83
84
- $ isHeuristicallyCacheable = $ response ->headers ->hasCacheControlDirective ('public ' );
85
84
$ maxAge = $ response ->headers ->hasCacheControlDirective ('max-age ' ) ? (int ) $ response ->headers ->getCacheControlDirective ('max-age ' ) : null ;
86
- $ this ->storeRelativeAgeDirective ('max-age ' , $ maxAge , $ age , $ isHeuristicallyCacheable );
87
85
$ sharedMaxAge = $ response ->headers ->hasCacheControlDirective ('s-maxage ' ) ? (int ) $ response ->headers ->getCacheControlDirective ('s-maxage ' ) : $ maxAge ;
88
- $ this ->storeRelativeAgeDirective ('s-maxage ' , $ sharedMaxAge , $ age , $ isHeuristicallyCacheable );
89
-
90
86
$ expires = $ response ->getExpires ();
91
87
$ expires = null !== $ expires ? (int ) $ expires ->format ('U ' ) - (int ) $ response ->getDate ()->format ('U ' ) : null ;
92
- $ this ->storeRelativeAgeDirective ('expires ' , $ expires >= 0 ? $ expires : null , 0 , $ isHeuristicallyCacheable );
88
+
89
+ // See https://datatracker.ietf.org/doc/html/rfc7234#section-4.2.2
90
+ // If a response is "public" but does not have maximum lifetime, heuristics might be applied.
91
+ // Do not store NULL values so the final response can have more limiting value from other responses.
92
+ $ isHeuristicallyCacheable = $ response ->headers ->hasCacheControlDirective ('public ' )
93
+ && null === $ maxAge
94
+ && null === $ sharedMaxAge
95
+ && null === $ expires ;
96
+
97
+ if (!$ isHeuristicallyCacheable || null !== $ maxAge || null !== $ expires ) {
98
+ $ this ->storeRelativeAgeDirective ('max-age ' , $ maxAge , $ expires , $ age );
99
+ }
100
+
101
+ if (!$ isHeuristicallyCacheable || null !== $ sharedMaxAge || null !== $ expires ) {
102
+ $ this ->storeRelativeAgeDirective ('s-maxage ' , $ sharedMaxAge , $ expires , $ age );
103
+ }
104
+
105
+ if (null !== $ expires ) {
106
+ $ this ->ageDirectives ['expires ' ] = true ;
107
+ }
93
108
}
94
109
95
110
/**
@@ -102,7 +117,7 @@ public function update(Response $response)
102
117
return ;
103
118
}
104
119
105
- // Remove validation related headers of the master response,
120
+ // Remove validation related headers of the final response,
106
121
// because some of the response content comes from at least
107
122
// one embedded response (which likely has a different caching strategy).
108
123
$ response ->setEtag (null );
@@ -145,9 +160,9 @@ public function update(Response $response)
145
160
}
146
161
}
147
162
148
- if (is_numeric ( $ this ->ageDirectives ['expires ' ]) ) {
163
+ if ($ this ->ageDirectives ['expires ' ] && null !== $ maxAge ) {
149
164
$ date = clone $ response ->getDate ();
150
- $ date = $ date ->modify ('+ ' .( $ this -> ageDirectives [ ' expires ' ] + $ this -> age ) .' seconds ' );
165
+ $ date = $ date ->modify ('+ ' .$ maxAge .' seconds ' );
151
166
$ response ->setExpires ($ date );
152
167
}
153
168
}
@@ -200,33 +215,16 @@ private function willMakeFinalResponseUncacheable(Response $response): bool
200
215
* we have to subtract the age so that the value is normalized for an age of 0.
201
216
*
202
217
* If the value is lower than the currently stored value, we update the value, to keep a rolling
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.
218
+ * minimal value of each instruction. If the value is NULL, the directive will not be set on the final response.
213
219
*/
214
- private function storeRelativeAgeDirective (string $ directive , ?int $ value , int $ age , bool $ isHeuristicallyCacheable )
220
+ private function storeRelativeAgeDirective (string $ directive , ?int $ value , ? int $ expires , int $ age ): void
215
221
{
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
- }
222
+ if (null === $ value && null === $ expires ) {
226
223
$ this ->ageDirectives [$ directive ] = false ;
227
224
}
228
225
229
226
if (false !== $ this ->ageDirectives [$ directive ]) {
227
+ $ value = min ($ value ?? PHP_INT_MAX , $ expires ?? PHP_INT_MAX );
230
228
$ value -= $ age ;
231
229
$ this ->ageDirectives [$ directive ] = null !== $ this ->ageDirectives [$ directive ] ? min ($ this ->ageDirectives [$ directive ], $ value ) : $ value ;
232
230
}
0 commit comments