Skip to content

feat(frankenphp): add response runner #178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions psalm.baseline.xml
Original file line number Diff line number Diff line change
@@ -20,6 +20,11 @@
<code><![CDATA[$options]]></code>
</InvalidArgument>
</file>
<file src="src/frankenphp-symfony/src/ResponseRunner.php">
<UndefinedFunction>
<code><![CDATA[\frankenphp_handle_request($handler)]]></code>
</UndefinedFunction>
</file>
<file src="src/google-cloud/router.php">
<MissingFile>
<code><![CDATA[require_once $_SERVER['SCRIPT_FILENAME'] = $defaultSource]]></code>
55 changes: 55 additions & 0 deletions src/frankenphp-symfony/src/ResponseRunner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Runtime\FrankenPhpSymfony;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Runtime\RunnerInterface;

/**
* A response runner for FrankenPHP.
*
* @author Kévin Dunglas <kevin@dunglas.dev>
*/
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();

Check failure on line 37 in src/frankenphp-symfony/src/ResponseRunner.php

GitHub Actions / Psalm

UndefinedFunction

src/frankenphp-symfony/src/ResponseRunner.php:37:17: UndefinedFunction: Function Runtime\FrankenPhpSymfony\xdebug_connect_to_client does not exist (see https://psalm.dev/021)
}

// 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;
}
}
11 changes: 9 additions & 2 deletions src/frankenphp-symfony/src/Runtime.php
Original file line number Diff line number Diff line change
@@ -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);
12 changes: 12 additions & 0 deletions src/frankenphp-symfony/tests/RunnerTest.php
Original file line number Diff line number Diff line change
@@ -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);
16 changes: 16 additions & 0 deletions src/frankenphp-symfony/tests/RuntimeTest.php
Original file line number Diff line number Diff line change
@@ -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));
}
}