diff --git a/psalm.baseline.xml b/psalm.baseline.xml index cc89e29..3576b5f 100644 --- a/psalm.baseline.xml +++ b/psalm.baseline.xml @@ -1,5 +1,5 @@ - + @@ -50,9 +50,25 @@ + + + getExpiresTime()]]> + isHttpOnly()]]> + isSecure()]]> + + + + + + getExpiresTime()]]> + isHttpOnly()]]> + isSecure()]]> getStatusCode()]]> + + + - + \ No newline at end of file diff --git a/src/swoole-nyholm/src/RequestHandlerRunner.php b/src/swoole-nyholm/src/RequestHandlerRunner.php index 805470a..3b20932 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,12 +51,28 @@ public function handle(Request $request, Response $response): void $response->setStatusCode($psrResponse->getStatusCode(), $psrResponse->getReasonPhrase()); - foreach ($psrResponse->getHeaders() as $name => $values) { + foreach ($psrResponse->withoutHeader('set-cookies')->getHeaders() as $name => $values) { foreach ($values as $value) { $response->setHeader($name, $value); } } + 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(); 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'); 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) { diff --git a/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php b/src/swoole/tests/Unit/SymfonyHttpBridgeTest.php index f025dc5..101b487 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,18 +64,20 @@ 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], + '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'); $response = $this->createMock(Response::class); $expectedHeaders = [ - ['x-test', 'Swoole-Runtime'], - ['set-cookie', $fooCookie], - ['set-cookie', $barCookie], + ['X-Test', 'Swoole-Runtime'], + ['Cache-Control', 'no-cache, private'], + ['Date', 'Thu, 25 Dec 2025 19:20:06 GMT'], ]; $callCount = 0; $response->expects(self::exactly(3))->method('header') @@ -85,6 +87,20 @@ public function testThatSymfonyResponseIsReflected(): void $this->assertEquals($expectedHeaders[$callCount][1], $value); ++$callCount; + return true; + }); + $expectedCookies = [ + ['foo', '123'], + ['bar', '234'], + ]; + $callCountCookie = 0; + $response->expects(self::exactly(2))->method('cookie') + ->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; }); $response->expects(self::once())->method('status')->with(201);