Skip to content

Commit

Permalink
add response runner in frankenphp (php-runtime#177)
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaume-sainthillier committed Nov 18, 2024
1 parent 2246529 commit 38bac35
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 2 deletions.
5 changes: 5 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions psalm.baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
<code>$options</code>
</InvalidArgument>
</file>
<file src="src/frankenphp-symfony/src/ResponseRunner.php">
<UndefinedVariable>
<code>$sfRequest</code>
</UndefinedVariable>
<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>
Expand Down
49 changes: 49 additions & 0 deletions src/frankenphp-symfony/src/ResponseRunner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?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 <[email protected]>
*/
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;
}
}
11 changes: 9 additions & 2 deletions src/frankenphp-symfony/src/Runtime.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions src/frankenphp-symfony/tests/RunnerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
16 changes: 16 additions & 0 deletions src/frankenphp-symfony/tests/RuntimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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();
Expand All @@ -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));
}
}

0 comments on commit 38bac35

Please sign in to comment.