diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index a98bb95..c544185 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -14,3 +14,8 @@ parameters:
message: "#^Function frankenphp_handle_request not found\\.$#"
count: 1
path: src/frankenphp-symfony/src/Runner.php
+
+ -
+ message: "#^Function frankenphp_handle_request not found\\.$#"
+ count: 1
+ path: src/frankenphp-symfony/src/ResponseRunner.php
diff --git a/psalm.baseline.xml b/psalm.baseline.xml
index cc89e29..8b41bd5 100644
--- a/psalm.baseline.xml
+++ b/psalm.baseline.xml
@@ -20,6 +20,11 @@
+
+
+
+
+
diff --git a/src/frankenphp-symfony/src/ResponseRunner.php b/src/frankenphp-symfony/src/ResponseRunner.php
new file mode 100644
index 0000000..ecdc373
--- /dev/null
+++ b/src/frankenphp-symfony/src/ResponseRunner.php
@@ -0,0 +1,55 @@
+
+ */
+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);
+
+ $xdebugConnectToClient = function_exists('xdebug_connect_to_client');
+
+ $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, $xdebugConnectToClient): void {
+ // Connect to the Xdebug client if it's available
+ if ($xdebugConnectToClient) {
+ xdebug_connect_to_client();
+ }
+
+ // Merge the environment variables coming from DotEnv with the ones tied to the current request
+ $_SERVER += $server;
+
+ $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));
+ }
}