Skip to content
New issue

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

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

Already on GitHub? # to your account

feat(frankenphp): add response runner #178

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
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 <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);

$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));
}
}
Loading