Skip to content

Commit 5772fc9

Browse files
add response runner in frankenphp (#177)
1 parent ff3cfe4 commit 5772fc9

File tree

6 files changed

+110
-2
lines changed

6 files changed

+110
-2
lines changed

phpstan-baseline.neon

Lines changed: 5 additions & 0 deletions
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

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

src/frankenphp-symfony/src/Runtime.php

Lines changed: 9 additions & 2 deletions
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

Lines changed: 12 additions & 0 deletions
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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,23 @@
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
/**
1315
* @author Kévin Dunglas <kevin@dunglas.dev>
1416
*/
1517
final class RuntimeTest extends TestCase
1618
{
19+
protected function setUp(): void
20+
{
21+
parent::setUp();
22+
unset($_SERVER['FRANKENPHP_WORKER']);
23+
}
24+
1725
public function testGetRunner(): void
1826
{
1927
$application = $this->createStub(HttpKernelInterface::class);
@@ -25,4 +33,16 @@ public function testGetRunner(): void
2533
$_SERVER['FRANKENPHP_WORKER'] = 1;
2634
$this->assertInstanceOf(Runner::class, $runtime->getRunner($application));
2735
}
36+
37+
public function testGetResponseRunner(): void
38+
{
39+
$application = $this->createStub(Response::class);
40+
41+
$runtime = new Runtime();
42+
$this->assertNotInstanceOf(ResponseRunner::class, $runtime->getRunner(null));
43+
$this->assertNotInstanceOf(ResponseRunner::class, $runtime->getRunner($application));
44+
45+
$_SERVER['FRANKENPHP_WORKER'] = 1;
46+
$this->assertInstanceOf(ResponseRunner::class, $runtime->getRunner($application));
47+
}
2848
}

0 commit comments

Comments
 (0)