From 8bdb775a8138125d373a2a0914217cb3739247b5 Mon Sep 17 00:00:00 2001 From: Porraphit Chuasuk Date: Fri, 26 Dec 2025 00:39:10 +0700 Subject: [PATCH 01/10] Handle cookie separately. --- src/swoole-nyholm/src/RequestHandlerRunner.php | 15 ++++++++++++++- src/swoole/src/SymfonyHttpBridge.php | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/swoole-nyholm/src/RequestHandlerRunner.php b/src/swoole-nyholm/src/RequestHandlerRunner.php index 805470a..dca98a8 100644 --- a/src/swoole-nyholm/src/RequestHandlerRunner.php +++ b/src/swoole-nyholm/src/RequestHandlerRunner.php @@ -50,12 +50,25 @@ public function handle(Request $request, Response $response): void $response->setStatusCode($psrResponse->getStatusCode(), $psrResponse->getReasonPhrase()); - foreach ($psrResponse->getHeaders() as $name => $values) { + foreach ($psrResponse->allPreserveCaseWithoutCookies() as $name => $values) { foreach ($values as $value) { $response->setHeader($name, $value); } } + foreach ($psrResponse->headers->getCookies() as $cookie) { + $response->cookie( + $cookie->getName(), + $cookie->getValue() ?? '', + $cookie->getExpiresTime(), + $cookie->getPath(), + $cookie->getDomain() ?? '', + $cookie->isSecure(), + $cookie->isHttpOnly(), + $cookie->getSameSite() ?? '' + ); + } + $body = $psrResponse->getBody(); $body->rewind(); diff --git a/src/swoole/src/SymfonyHttpBridge.php b/src/swoole/src/SymfonyHttpBridge.php index 27303f6..7c38c44 100644 --- a/src/swoole/src/SymfonyHttpBridge.php +++ b/src/swoole/src/SymfonyHttpBridge.php @@ -37,12 +37,25 @@ public static function convertSwooleRequest(Request $request): SymfonyRequest public static function reflectSymfonyResponse(SymfonyResponse $sfResponse, Response $response): void { - foreach ($sfResponse->headers->all() as $name => $values) { + foreach ($sfResponse->headers->allPreserveCaseWithoutCookies() as $name => $values) { foreach ((array) $values as $value) { $response->header((string) $name, $value); } } + foreach ($sfResponse->headers->getCookies() as $cookie) { + $response->cookie( + $cookie->getName(), + $cookie->getValue() ?? '', + $cookie->getExpiresTime(), + $cookie->getPath(), + $cookie->getDomain() ?? '', + $cookie->isSecure(), + $cookie->isHttpOnly(), + $cookie->getSameSite() ?? '' + ); + } + $response->status($sfResponse->getStatusCode()); switch (true) { From 1bb0e0b8091c16e3ed6d7dbb2bca27cc4477ca37 Mon Sep 17 00:00:00 2001 From: Poraphit Date: Fri, 26 Dec 2025 01:23:43 +0700 Subject: [PATCH 02/10] Fixed nyholm response implementation --- .../src/RequestHandlerRunner.php | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/swoole-nyholm/src/RequestHandlerRunner.php b/src/swoole-nyholm/src/RequestHandlerRunner.php index dca98a8..50e57d1 100644 --- a/src/swoole-nyholm/src/RequestHandlerRunner.php +++ b/src/swoole-nyholm/src/RequestHandlerRunner.php @@ -5,6 +5,7 @@ use Psr\Http\Server\RequestHandlerInterface; use Swoole\Http\Request; use Swoole\Http\Response; +use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\Runtime\RunnerInterface; class RequestHandlerRunner implements RunnerInterface @@ -50,25 +51,29 @@ public function handle(Request $request, Response $response): void $response->setStatusCode($psrResponse->getStatusCode(), $psrResponse->getReasonPhrase()); - foreach ($psrResponse->allPreserveCaseWithoutCookies() as $name => $values) { + foreach ($psrResponse->withoutHeader('set-cookies')->getHeaders() as $name => $values) { foreach ($values as $value) { $response->setHeader($name, $value); } } - foreach ($psrResponse->headers->getCookies() as $cookie) { - $response->cookie( - $cookie->getName(), - $cookie->getValue() ?? '', - $cookie->getExpiresTime(), - $cookie->getPath(), - $cookie->getDomain() ?? '', - $cookie->isSecure(), - $cookie->isHttpOnly(), - $cookie->getSameSite() ?? '' - ); + if ($psrResponse->hasHeader('set-cookies')) { + foreach ($psrResponse->getHeader('set-cookies') as $cookieString) { + $cookie = Cookie::fromString($cookieString); + $response->cookie( + $cookie->getName(), + $cookie->getValue() ?? '', + $cookie->getExpiresTime(), + $cookie->getPath(), + $cookie->getDomain() ?? '', + $cookie->isSecure(), + $cookie->isHttpOnly(), + $cookie->getSameSite() ?? '' + ); + } } + $body = $psrResponse->getBody(); $body->rewind(); From 9d2231882c3b7d9a2b2e438d802bfe7b08031f2f Mon Sep 17 00:00:00 2001 From: Poraphit Date: Fri, 26 Dec 2025 01:53:50 +0700 Subject: [PATCH 03/10] Ignore paslm warning on swoole cookie method. --- psalm.baseline.xml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/psalm.baseline.xml b/psalm.baseline.xml index cc89e29..4e0e5e9 100644 --- a/psalm.baseline.xml +++ b/psalm.baseline.xml @@ -54,5 +54,31 @@ getStatusCode()]]> + + cookie]]> + + + getExpiresTime()]]> + + + isHttpOnly()]]> + + + getSameSite()]]> + + + + + cookie]]> + + + getExpiresTime()]]> + + + isHttpOnly()]]> + + + getSameSite()]]> + From 0dcd6da0c5d6318b72204a0f2c3085564cf36815 Mon Sep 17 00:00:00 2001 From: Poraphit Date: Fri, 26 Dec 2025 01:58:46 +0700 Subject: [PATCH 04/10] Correct the inner header class for test --- src/swoole/tests/Unit/SymfonyHttpBridgeTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php b/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php index f025dc5..e2ac25a 100644 --- a/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php +++ b/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php @@ -9,9 +9,9 @@ use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\File\UploadedFile; -use Symfony\Component\HttpFoundation\HeaderBag; use Symfony\Component\HttpFoundation\Request as SymfonyRequest; use Symfony\Component\HttpFoundation\Response as SymfonyResponse; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\HttpFoundation\StreamedResponse; /** @@ -64,7 +64,7 @@ public function testThatSymfonyResponseIsReflected(): void $barCookie = (string) new Cookie('bar', '234'); $sfResponse = $this->createMock(SymfonyResponse::class); - $sfResponse->headers = new HeaderBag([ + $sfResponse->headers = new ResponseHeaderBag([ 'X-Test' => 'Swoole-Runtime', 'Set-Cookie' => [$fooCookie, $barCookie], ]); From ce2f10aa8f14cdd6494392f585b04ac69c43ed5d Mon Sep 17 00:00:00 2001 From: Poraphit Date: Fri, 26 Dec 2025 02:01:31 +0700 Subject: [PATCH 05/10] Infer the expectation. --- src/swoole/tests/Unit/SymfonyHttpBridgeTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php b/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php index e2ac25a..13bc749 100644 --- a/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php +++ b/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php @@ -73,9 +73,9 @@ public function testThatSymfonyResponseIsReflected(): void $response = $this->createMock(Response::class); $expectedHeaders = [ - ['x-test', 'Swoole-Runtime'], - ['set-cookie', $fooCookie], - ['set-cookie', $barCookie], + ['X-Test', 'Swoole-Runtime'], + ['Set-Cookie', $fooCookie], + ['Set-Cookie', $barCookie], ]; $callCount = 0; $response->expects(self::exactly(3))->method('header') From 27b98156a7ea07b1bda60128db53a13dfae1a397 Mon Sep 17 00:00:00 2001 From: Poraphit Date: Fri, 26 Dec 2025 02:15:45 +0700 Subject: [PATCH 06/10] Add test for cookie separately --- .../tests/Unit/SymfonyHttpBridgeTest.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php b/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php index 13bc749..2bc2a66 100644 --- a/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php +++ b/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php @@ -74,17 +74,29 @@ public function testThatSymfonyResponseIsReflected(): void $response = $this->createMock(Response::class); $expectedHeaders = [ ['X-Test', 'Swoole-Runtime'], - ['Set-Cookie', $fooCookie], - ['Set-Cookie', $barCookie], ]; $callCount = 0; - $response->expects(self::exactly(3))->method('header') + $response->expects(self::exactly(1))->method('header') ->willReturnCallback(function ($key, $value) use ($expectedHeaders, &$callCount) { $this->assertArrayHasKey($callCount, $expectedHeaders); $this->assertEquals($expectedHeaders[$callCount][0], $key); $this->assertEquals($expectedHeaders[$callCount][1], $value); ++$callCount; + return true; + }); + $expectedCookies = [ + ['foo', '123'], + ['bar', '234'], + ]; + $callCount = 0; + $response->expects(self::exactly(2))->method('cookie') + ->willReturnCallback(function ($name, $value) use ($expectedCookies, &$callCount) { + $this->assertArrayHasKey($callCount, $expectedCookies); + $this->assertEquals($expectedCookies[$callCount][0], $name); + $this->assertEquals($expectedCookies[$callCount][1], $value); + ++$callCount; + return true; }); $response->expects(self::once())->method('status')->with(201); From f2333bbcdb2c3e47d5cfb3e7c50a59b192a54441 Mon Sep 17 00:00:00 2001 From: Poraphit Date: Fri, 26 Dec 2025 02:24:02 +0700 Subject: [PATCH 07/10] Add default header when test. --- .../tests/Unit/SymfonyHttpBridgeTest.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php b/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php index 2bc2a66..101b487 100644 --- a/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php +++ b/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php @@ -67,6 +67,8 @@ public function testThatSymfonyResponseIsReflected(): void $sfResponse->headers = new ResponseHeaderBag([ 'X-Test' => 'Swoole-Runtime', 'Set-Cookie' => [$fooCookie, $barCookie], + 'Cache-Control' => 'no-cache, private', + 'Date' => 'Thu, 25 Dec 2025 19:20:06 GMT', ]); $sfResponse->expects(self::once())->method('getStatusCode')->willReturn(201); $sfResponse->expects(self::once())->method('getContent')->willReturn('Test'); @@ -74,9 +76,11 @@ public function testThatSymfonyResponseIsReflected(): void $response = $this->createMock(Response::class); $expectedHeaders = [ ['X-Test', 'Swoole-Runtime'], + ['Cache-Control', 'no-cache, private'], + ['Date', 'Thu, 25 Dec 2025 19:20:06 GMT'], ]; $callCount = 0; - $response->expects(self::exactly(1))->method('header') + $response->expects(self::exactly(3))->method('header') ->willReturnCallback(function ($key, $value) use ($expectedHeaders, &$callCount) { $this->assertArrayHasKey($callCount, $expectedHeaders); $this->assertEquals($expectedHeaders[$callCount][0], $key); @@ -89,13 +93,13 @@ public function testThatSymfonyResponseIsReflected(): void ['foo', '123'], ['bar', '234'], ]; - $callCount = 0; + $callCountCookie = 0; $response->expects(self::exactly(2))->method('cookie') - ->willReturnCallback(function ($name, $value) use ($expectedCookies, &$callCount) { - $this->assertArrayHasKey($callCount, $expectedCookies); - $this->assertEquals($expectedCookies[$callCount][0], $name); - $this->assertEquals($expectedCookies[$callCount][1], $value); - ++$callCount; + ->willReturnCallback(function ($name, $value) use ($expectedCookies, &$callCountCookie) { + $this->assertArrayHasKey($callCountCookie, $expectedCookies); + $this->assertEquals($expectedCookies[$callCountCookie][0], $name); + $this->assertEquals($expectedCookies[$callCountCookie][1], $value); + ++$callCountCookie; return true; }); From fcdba22cf47740d0ea9c19081315326190da29d3 Mon Sep 17 00:00:00 2001 From: Poraphit Date: Fri, 26 Dec 2025 02:30:01 +0700 Subject: [PATCH 08/10] Update test flow --- src/swoole-nyholm/tests/Unit/RequestHandlerRunnerTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/swoole-nyholm/tests/Unit/RequestHandlerRunnerTest.php b/src/swoole-nyholm/tests/Unit/RequestHandlerRunnerTest.php index 3b8bd6d..c4da49d 100644 --- a/src/swoole-nyholm/tests/Unit/RequestHandlerRunnerTest.php +++ b/src/swoole-nyholm/tests/Unit/RequestHandlerRunnerTest.php @@ -31,6 +31,7 @@ public function testHandle(): void $factory = $this->createMock(ServerFactory::class); $application = $this->createMock(RequestHandlerInterface::class); $psrResponse = $this->createMock(ResponseInterface::class); + $psrResponseInner = $this->createMock(ResponseInterface::class); $request = $this->createMock(Request::class); $response = $this->createMock(Response::class); @@ -39,9 +40,10 @@ public function testHandle(): void $application->expects(self::once())->method('handle')->willReturn($psrResponse); - $psrResponse->expects(self::once())->method('getHeaders')->willReturn([ + $psrResponseInner->expects(self::once())->method('getHeaders')->willReturn([ 'X-Test' => ['Swoole-Runtime'], ]); + $psrResponse->expects(self::once())->method('withoutHeader')->willReturn($psrResponseInner); $psrResponse->expects(self::once())->method('getBody')->willReturn(Stream::create('Test')); $psrResponse->expects(self::once())->method('getStatusCode')->willReturn(200); $psrResponse->expects(self::once())->method('getReasonPhrase')->willReturn('OK'); From 8be387184591b401e02414b62a299a953675cec5 Mon Sep 17 00:00:00 2001 From: Poraphit Date: Fri, 26 Dec 2025 02:37:00 +0700 Subject: [PATCH 09/10] CS fixer --- src/swoole-nyholm/src/RequestHandlerRunner.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/swoole-nyholm/src/RequestHandlerRunner.php b/src/swoole-nyholm/src/RequestHandlerRunner.php index 50e57d1..3b20932 100644 --- a/src/swoole-nyholm/src/RequestHandlerRunner.php +++ b/src/swoole-nyholm/src/RequestHandlerRunner.php @@ -73,7 +73,6 @@ public function handle(Request $request, Response $response): void } } - $body = $psrResponse->getBody(); $body->rewind(); From 45b967c7c076fe9855318116317470c64f199cb7 Mon Sep 17 00:00:00 2001 From: Poraphit Date: Fri, 26 Dec 2025 03:52:20 +0700 Subject: [PATCH 10/10] Fixed the psalm. --- psalm.baseline.xml | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/psalm.baseline.xml b/psalm.baseline.xml index 4e0e5e9..3576b5f 100644 --- a/psalm.baseline.xml +++ b/psalm.baseline.xml @@ -1,5 +1,5 @@ - + @@ -50,35 +50,25 @@ - - - getStatusCode()]]> - - - cookie]]> - + getExpiresTime()]]> - - isHttpOnly()]]> + isSecure()]]> - - getSameSite()]]> - - - - cookie]]> + + + getExpiresTime()]]> - - isHttpOnly()]]> + isSecure()]]> + getStatusCode()]]> - - getSameSite()]]> - + + + - + \ No newline at end of file