Skip to content

Commit 861cac8

Browse files
add response runner in frankenphp (#177)
1 parent f00cf6c commit 861cac8

File tree

6 files changed

+102
-2
lines changed

6 files changed

+102
-2
lines changed

phpstan-baseline.neon

+5
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,8 @@ parameters:
1414
message: "#^Function frankenphp_handle_request not found\\.$#"
1515
count: 1
1616
path: src/frankenphp-symfony/src/Runner.php
17+
18+
-
19+
message: "#^Function frankenphp_handle_request not found\\.$#"
20+
count: 1
21+
path: src/frankenphp-symfony/src/ResponseRunner.php

psalm.baseline.xml

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
<code><![CDATA[$options]]></code>
2121
</InvalidArgument>
2222
</file>
23+
<file src="src/frankenphp-symfony/src/ResponseRunner.php">
24+
<UndefinedFunction>
25+
<code><![CDATA[\frankenphp_handle_request($handler)]]></code>
26+
</UndefinedFunction>
27+
</file>
2328
<file src="src/google-cloud/router.php">
2429
<MissingFile>
2530
<code><![CDATA[require_once $_SERVER['SCRIPT_FILENAME'] = $defaultSource]]></code>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Runtime\FrankenPhpSymfony;
6+
7+
use Symfony\Component\HttpFoundation\Request;
8+
use Symfony\Component\HttpFoundation\Response;
9+
use Symfony\Component\Runtime\RunnerInterface;
10+
11+
/**
12+
* A response runner for FrankenPHP.
13+
*
14+
* @author Kévin Dunglas <[email protected]>
15+
*/
16+
class ResponseRunner implements RunnerInterface
17+
{
18+
public function __construct(
19+
private Response $response,
20+
private int $loopMax,
21+
) {
22+
}
23+
24+
public function run(): int
25+
{
26+
// Prevent worker script termination when a client connection is interrupted
27+
ignore_user_abort(true);
28+
29+
$xdebugConnectToClient = function_exists('xdebug_connect_to_client');
30+
31+
$server = array_filter($_SERVER, static fn (string $key) => !str_starts_with($key, 'HTTP_'), ARRAY_FILTER_USE_KEY);
32+
$server['APP_RUNTIME_MODE'] = 'web=1&worker=1';
33+
34+
$handler = function () use ($server, $xdebugConnectToClient): void {
35+
// Connect to the Xdebug client if it's available
36+
if ($xdebugConnectToClient) {
37+
xdebug_connect_to_client();
38+
}
39+
40+
// Merge the environment variables coming from DotEnv with the ones tied to the current request
41+
$_SERVER += $server;
42+
43+
$this->response->send();
44+
};
45+
46+
$loops = 0;
47+
do {
48+
$ret = \frankenphp_handle_request($handler);
49+
50+
gc_collect_cycles();
51+
} while ($ret && (-1 === $this->loopMax || ++$loops < $this->loopMax));
52+
53+
return 0;
54+
}
55+
}

src/frankenphp-symfony/src/Runtime.php

+9-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Runtime\FrankenPhpSymfony;
66

7+
use Symfony\Component\HttpFoundation\Response;
78
use Symfony\Component\HttpKernel\HttpKernelInterface;
89
use Symfony\Component\Runtime\RunnerInterface;
910
use Symfony\Component\Runtime\SymfonyRuntime;
@@ -29,8 +30,14 @@ public function __construct(array $options = [])
2930

3031
public function getRunner(?object $application): RunnerInterface
3132
{
32-
if ($application instanceof HttpKernelInterface && ($_SERVER['FRANKENPHP_WORKER'] ?? false)) {
33-
return new Runner($application, $this->options['frankenphp_loop_max']);
33+
if ($_SERVER['FRANKENPHP_WORKER'] ?? false) {
34+
if ($application instanceof HttpKernelInterface) {
35+
return new Runner($application, $this->options['frankenphp_loop_max']);
36+
}
37+
38+
if ($application instanceof Response) {
39+
return new ResponseRunner($application, $this->options['frankenphp_loop_max']);
40+
}
3441
}
3542

3643
return parent::getRunner($application);

src/frankenphp-symfony/tests/RunnerTest.php

+12
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
require_once __DIR__.'/function-mock.php';
88

99
use PHPUnit\Framework\TestCase;
10+
use Runtime\FrankenPhpSymfony\ResponseRunner;
1011
use Runtime\FrankenPhpSymfony\Runner;
1112
use Symfony\Component\HttpFoundation\Request;
1213
use Symfony\Component\HttpFoundation\Response;
@@ -22,6 +23,17 @@ interface TestAppInterface extends HttpKernelInterface, TerminableInterface
2223
*/
2324
class RunnerTest extends TestCase
2425
{
26+
public function testResponseRun(): void
27+
{
28+
$application = $this->createMock(Response::class);
29+
$application
30+
->expects($this->once())
31+
->method('send');
32+
33+
$runner = new ResponseRunner($application, 500);
34+
$this->assertSame(0, $runner->run());
35+
}
36+
2537
public function testRun(): void
2638
{
2739
$application = $this->createMock(TestAppInterface::class);

src/frankenphp-symfony/tests/RuntimeTest.php

+16
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
namespace Runtime\FrankenPhpSymfony\Tests;
66

77
use PHPUnit\Framework\TestCase;
8+
use Runtime\FrankenPhpSymfony\ResponseRunner;
89
use Runtime\FrankenPhpSymfony\Runner;
910
use Runtime\FrankenPhpSymfony\Runtime;
11+
use Symfony\Component\HttpFoundation\Response;
1012
use Symfony\Component\HttpKernel\HttpKernelInterface;
1113

1214
/**
@@ -16,6 +18,7 @@ final class RuntimeTest extends TestCase
1618
{
1719
public function testGetRunner(): void
1820
{
21+
unset($_SERVER['FRANKENPHP_WORKER']);
1922
$application = $this->createStub(HttpKernelInterface::class);
2023

2124
$runtime = new Runtime();
@@ -25,4 +28,17 @@ public function testGetRunner(): void
2528
$_SERVER['FRANKENPHP_WORKER'] = 1;
2629
$this->assertInstanceOf(Runner::class, $runtime->getRunner($application));
2730
}
31+
32+
public function testGetResponseRunner(): void
33+
{
34+
unset($_SERVER['FRANKENPHP_WORKER']);
35+
$application = $this->createStub(Response::class);
36+
37+
$runtime = new Runtime();
38+
$this->assertNotInstanceOf(ResponseRunner::class, $runtime->getRunner(null));
39+
$this->assertNotInstanceOf(ResponseRunner::class, $runtime->getRunner($application));
40+
41+
$_SERVER['FRANKENPHP_WORKER'] = 1;
42+
$this->assertInstanceOf(ResponseRunner::class, $runtime->getRunner($application));
43+
}
2844
}

0 commit comments

Comments
 (0)