From 3e2f1e9961a907d3db4ac22ad64d59ab23cdce74 Mon Sep 17 00:00:00 2001 From: Guillaume Sainthillier Date: Mon, 18 Nov 2024 11:11:01 +0100 Subject: [PATCH] add response runner in frankenphp (#177) --- src/frankenphp-symfony/src/ResponseRunner.php | 49 +++++++++++++++++++ src/frankenphp-symfony/src/Runtime.php | 11 ++++- src/frankenphp-symfony/tests/RunnerTest.php | 12 +++++ src/frankenphp-symfony/tests/RuntimeTest.php | 16 ++++++ 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 src/frankenphp-symfony/src/ResponseRunner.php diff --git a/src/frankenphp-symfony/src/ResponseRunner.php b/src/frankenphp-symfony/src/ResponseRunner.php new file mode 100644 index 0000000..124e8f4 --- /dev/null +++ b/src/frankenphp-symfony/src/ResponseRunner.php @@ -0,0 +1,49 @@ + + */ +class ResponseRunner implements RunnerInterface +{ + public function __construct( + private Response $response, + private int $loopMax, + ) { + } + + public function run(): int + { + // Prevent worker script termination when a client connection is interrupted + ignore_user_abort(true); + + $server = array_filter($_SERVER, static fn (string $key) => !str_starts_with($key, 'HTTP_'), ARRAY_FILTER_USE_KEY); + $server['APP_RUNTIME_MODE'] = 'web=1&worker=1'; + + $handler = function () use ($server, &$sfRequest): void { + // Merge the environment variables coming from DotEnv with the ones tied to the current request + $_SERVER += $server; + + $sfRequest = Request::createFromGlobals(); + $this->response->send(); + }; + + $loops = 0; + do { + $ret = \frankenphp_handle_request($handler); + + gc_collect_cycles(); + } while ($ret && (-1 === $this->loopMax || ++$loops < $this->loopMax)); + + return 0; + } +} diff --git a/src/frankenphp-symfony/src/Runtime.php b/src/frankenphp-symfony/src/Runtime.php index efae98b..1a8bf25 100644 --- a/src/frankenphp-symfony/src/Runtime.php +++ b/src/frankenphp-symfony/src/Runtime.php @@ -4,6 +4,7 @@ namespace Runtime\FrankenPhpSymfony; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Runtime\RunnerInterface; use Symfony\Component\Runtime\SymfonyRuntime; @@ -29,8 +30,14 @@ public function __construct(array $options = []) public function getRunner(?object $application): RunnerInterface { - if ($application instanceof HttpKernelInterface && ($_SERVER['FRANKENPHP_WORKER'] ?? false)) { - return new Runner($application, $this->options['frankenphp_loop_max']); + if ($_SERVER['FRANKENPHP_WORKER'] ?? false) { + if ($application instanceof HttpKernelInterface) { + return new Runner($application, $this->options['frankenphp_loop_max']); + } + + if ($application instanceof Response) { + return new ResponseRunner($application, $this->options['frankenphp_loop_max']); + } } return parent::getRunner($application); diff --git a/src/frankenphp-symfony/tests/RunnerTest.php b/src/frankenphp-symfony/tests/RunnerTest.php index 8d3caa7..9440f1b 100644 --- a/src/frankenphp-symfony/tests/RunnerTest.php +++ b/src/frankenphp-symfony/tests/RunnerTest.php @@ -7,6 +7,7 @@ require_once __DIR__.'/function-mock.php'; use PHPUnit\Framework\TestCase; +use Runtime\FrankenPhpSymfony\ResponseRunner; use Runtime\FrankenPhpSymfony\Runner; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -22,6 +23,17 @@ interface TestAppInterface extends HttpKernelInterface, TerminableInterface */ class RunnerTest extends TestCase { + public function testResponseRun(): void + { + $application = $this->createMock(Response::class); + $application + ->expects($this->once()) + ->method('send'); + + $runner = new ResponseRunner($application, 500); + $this->assertSame(0, $runner->run()); + } + public function testRun(): void { $application = $this->createMock(TestAppInterface::class); diff --git a/src/frankenphp-symfony/tests/RuntimeTest.php b/src/frankenphp-symfony/tests/RuntimeTest.php index cc91527..c11e419 100644 --- a/src/frankenphp-symfony/tests/RuntimeTest.php +++ b/src/frankenphp-symfony/tests/RuntimeTest.php @@ -5,8 +5,10 @@ namespace Runtime\FrankenPhpSymfony\Tests; use PHPUnit\Framework\TestCase; +use Runtime\FrankenPhpSymfony\ResponseRunner; use Runtime\FrankenPhpSymfony\Runner; use Runtime\FrankenPhpSymfony\Runtime; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\HttpKernelInterface; /** @@ -16,6 +18,7 @@ final class RuntimeTest extends TestCase { public function testGetRunner(): void { + unset($_SERVER['FRANKENPHP_WORKER']); $application = $this->createStub(HttpKernelInterface::class); $runtime = new Runtime(); @@ -25,4 +28,17 @@ public function testGetRunner(): void $_SERVER['FRANKENPHP_WORKER'] = 1; $this->assertInstanceOf(Runner::class, $runtime->getRunner($application)); } + + public function testGetResponseRunner(): void + { + unset($_SERVER['FRANKENPHP_WORKER']); + $application = $this->createStub(Response::class); + + $runtime = new Runtime(); + $this->assertNotInstanceOf(ResponseRunner::class, $runtime->getRunner(null)); + $this->assertNotInstanceOf(ResponseRunner::class, $runtime->getRunner($application)); + + $_SERVER['FRANKENPHP_WORKER'] = 1; + $this->assertInstanceOf(ResponseRunner::class, $runtime->getRunner($application)); + } }