2727use function is_string ;
2828use function strlen ;
2929use function strval ;
30- use function array_key_exists ;
31- use function is_int ;
3230use function is_file ;
3331use function strtolower ;
3432use function is_callable ;
33+ use function gmdate ;
34+ use function is_numeric ;
35+ use function rawurlencode ;
36+ use function str_replace ;
37+ use function trim ;
38+ use function ucfirst ;
3539
3640/**
3741 * response entity
@@ -228,7 +232,7 @@ public function cookie(string $name): mixed
228232
229233 /**
230234 * @param string $statusText
231- * @return $this
235+ * @return static
232236 */
233237 public function setStatusText (string $ statusText ): static
234238 {
@@ -238,7 +242,7 @@ public function setStatusText(string $statusText): static
238242
239243 /**
240244 * @param int $statusCode
241- * @return $this
245+ * @return static
242246 */
243247 public function setStatusCode (int $ statusCode ): static
244248 {
@@ -249,7 +253,7 @@ public function setStatusCode(int $statusCode): static
249253 /**
250254 * @param string $name
251255 * @param mixed $value
252- * @return $this
256+ * @return static
253257 */
254258 public function withHeader (string $ name , mixed $ value ): static
255259 {
@@ -258,15 +262,83 @@ public function withHeader(string $name, mixed $value): static
258262 }
259263
260264 /**
265+ * @param string $name
266+ * @param string $content
267+ * @param array|null $options
268+ * @return static
261269 */
262- public function withCookie (string $ name , array $ values ): static
270+ public function withCookie (string $ name , string $ content , ? array $ options = [] ): static
263271 {
264- $ this ->cookieLines [$ name ] = $ this ->buildCookieLine ($ name , $ values );
272+ $ options = $ options ?? [];
273+
274+ $ name = trim ($ name );
275+ if ($ name === '' ) {
276+ return $ this ;
277+ }
278+
279+ $ value = str_replace (['; ' , "\r" , "\n" , "\0" ], '' , rawurlencode ($ content ));
280+
281+ // 开始收集所有部分
282+ $ parts = ["{$ name }= {$ value }" ];
283+
284+ // expires
285+ if (isset ($ options ['expires ' ]) && is_numeric ($ options ['expires ' ])) {
286+ $ exp = (int ) $ options ['expires ' ];
287+ if ($ exp > 0 ) {
288+ $ expiresStr = gmdate ('D, d M Y H:i:s \G\M\T ' , $ exp );
289+ $ parts [] = "Expires= {$ expiresStr }" ;
290+ }
291+ }
292+
293+ // maxAge
294+ if (isset ($ options ['maxAge ' ]) && is_numeric ($ options ['maxAge ' ])) {
295+ $ maxAge = (int ) $ options ['maxAge ' ];
296+ $ parts [] = "Max-Age= {$ maxAge }" ;
297+ if ($ maxAge <= 0 ) {
298+ $ parts [] = 'Expires=Thu, 01 Jan 1970 00:00:00 GMT ' ;
299+ }
300+ }
301+
302+ // path - 默认 /
303+ $ path = !empty ($ options ['path ' ]) ? trim ($ options ['path ' ]) : '/ ' ;
304+ $ path = str_replace (['; ' , "\r" , "\n" ], '' , $ path );
305+ $ parts [] = "Path= {$ path }" ;
306+
307+ // domain
308+ if (!empty ($ options ['domain ' ])) {
309+ $ domain = trim ($ options ['domain ' ], '. ' );
310+ if ($ domain !== '' ) {
311+ $ domain = str_replace (['; ' , "\r" , "\n" ], '' , $ domain );
312+ $ parts [] = "Domain= {$ domain }" ;
313+ }
314+ }
315+
316+ // secure
317+ if (!empty ($ options ['secure ' ])) {
318+ $ parts [] = 'Secure ' ;
319+ }
320+
321+ // httponly
322+ if (!empty ($ options ['httponly ' ]) || !empty ($ options ['httpOnly ' ])) {
323+ $ parts [] = 'HttpOnly ' ;
324+ }
325+
326+ // samesite
327+ if (!empty ($ options ['samesite ' ])) {
328+ $ s = strtolower (trim ($ options ['samesite ' ]));
329+ if ($ s === 'strict ' || $ s === 'lax ' || $ s === 'none ' ) {
330+ $ s = ucfirst ($ s );
331+ $ parts [] = "SameSite= {$ s }" ;
332+ }
333+ }
334+
335+ $ this ->cookieLines [] = implode ('; ' , $ parts );
265336 return $ this ;
266337 }
267338
268339 /**
269- * @return $this
340+ * @param string $cookieLine
341+ * @return static
270342 */
271343 public function withCookieLine (string $ cookieLine ): static
272344 {
@@ -301,7 +373,7 @@ public function withBody(mixed $content): static
301373
302374 /**
303375 * @param Stream $stream
304- * @return $this
376+ * @return static
305377 */
306378 public function withStream (Stream $ stream ): static
307379 {
@@ -311,7 +383,7 @@ public function withStream(Stream $stream): static
311383
312384 /**
313385 * @param string $name
314- * @return $this
386+ * @return static
315387 */
316388 public function removeHeader (string $ name ): static
317389 {
@@ -321,7 +393,7 @@ public function removeHeader(string $name): static
321393
322394 /**
323395 * 响应体发送完成后关闭连接
324- * @return $this
396+ * @return static
325397 */
326398 public function closeAfter (): static
327399 {
@@ -337,44 +409,4 @@ public function body(): mixed
337409 {
338410 return $ this ->body ;
339411 }
340-
341- /**
342- * @param string $name
343- * @param array $values
344- * @return string
345- */
346- private function buildCookieLine (string $ name , array $ values ): string
347- {
348- $ segments = [];
349-
350- $ mainValue = $ values [$ name ] ?? $ values ['value ' ] ?? null ;
351- if (array_key_exists ($ name , $ values )) {
352- unset($ values [$ name ]);
353- }
354-
355- if ($ mainValue !== null ) {
356- $ segments [] = $ name . '= ' . $ mainValue ;
357- } else {
358- $ segments [] = $ name ;
359- }
360-
361- foreach ($ values as $ k => $ v ) {
362- if (is_int ($ k )) {
363- if ($ v !== null && $ v !== false && $ v !== '' ) {
364- $ segments [] = strval ($ v );
365- }
366- continue ;
367- }
368-
369- if ($ v === null || $ v === true ) {
370- $ segments [] = $ k ;
371- } elseif ($ v === false ) {
372- continue ;
373- } else {
374- $ segments [] = $ k . '= ' . $ v ;
375- }
376- }
377-
378- return implode ('; ' , $ segments );
379- }
380412}
0 commit comments