Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 41b4f54

Browse filesBrowse files
[Runtime] Automatically enable FrankenPHP runtime when its worker mode is detected
1 parent cb08480 commit 41b4f54
Copy full SHA for 41b4f54

8 files changed

+185
-1
lines changed

‎src/Symfony/Component/Runtime/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Runtime/CHANGELOG.md
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
CHANGELOG
22
=========
33

4+
7.3
5+
---
6+
7+
* Add `FrankenPhpRuntime` and `FrankenPhpRunner`
8+
* Add automatic detection of FrankenPHP worker mode to enable the correct runtime
9+
410
6.4
511
---
612

+35Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Symfony\Component\Runtime;
4+
5+
use Symfony\Component\HttpKernel\HttpKernelInterface;
6+
use Symfony\Component\Runtime\Runner\FrankenPhpRunner;
7+
8+
/**
9+
* A runtime for FrankenPHP.
10+
*
11+
* @author Kévin Dunglas <kevin@dunglas.dev>
12+
*/
13+
class FrankenPhpRuntime extends SymfonyRuntime
14+
{
15+
/**
16+
* @param array{
17+
* frankenphp_loop_max?: int,
18+
* } $options
19+
*/
20+
public function __construct(array $options = [])
21+
{
22+
$options['frankenphp_loop_max'] = (int) ($options['frankenphp_loop_max'] ?? $_SERVER['FRANKENPHP_LOOP_MAX'] ?? $_ENV['FRANKENPHP_LOOP_MAX'] ?? 500);
23+
24+
parent::__construct($options);
25+
}
26+
27+
public function getRunner(?object $application): RunnerInterface
28+
{
29+
if ($application instanceof HttpKernelInterface && ($_SERVER['FRANKENPHP_WORKER'] ?? false)) {
30+
return new FrankenPhpRunner($application, $this->options['frankenphp_loop_max']);
31+
}
32+
33+
return parent::getRunner($application);
34+
}
35+
}

‎src/Symfony/Component/Runtime/Internal/ComposerPlugin.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Runtime/Internal/ComposerPlugin.php
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Composer\Plugin\PluginInterface;
1919
use Composer\Script\ScriptEvents;
2020
use Symfony\Component\Filesystem\Filesystem;
21+
use Symfony\Component\Runtime\FrankenPhpRuntime;
2122
use Symfony\Component\Runtime\SymfonyRuntime;
2223

2324
/**
@@ -96,6 +97,7 @@ public function updateAutoloadFile(): void
9697
$code = strtr(file_get_contents($autoloadTemplate), [
9798
'%project_dir%' => $projectDir,
9899
'%runtime_class%' => var_export($runtimeClass, true),
100+
'%frankenphp_runtime_class%' => var_export(FrankenPhpRuntime::class, true),
99101
'%runtime_options%' => '['.substr(var_export($extra, true), 7, -1)." 'project_dir' => {$projectDir},\n]",
100102
]);
101103

‎src/Symfony/Component/Runtime/Internal/autoload_runtime.template

Copy file name to clipboardExpand all lines: src/Symfony/Component/Runtime/Internal/autoload_runtime.template
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ if (!is_object($app)) {
1212
throw new TypeError(sprintf('Invalid return value: callable object expected, "%s" returned from "%s".', get_debug_type($app), $_SERVER['SCRIPT_FILENAME']));
1313
}
1414

15-
$runtime = $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? %runtime_class%;
15+
if (null === ($runtime = $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? null)) {
16+
$runtime = ($_SERVER['FRANKENPHP_WORKER'] ?? $_ENV['FRANKENPHP_WORKER']) ? %frankenphp_runtime_class% : %runtime_class%;
17+
}
18+
1619
$runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + %runtime_options%);
1720

1821
[$app, $args] = $runtime
+61Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
namespace Symfony\Component\Runtime\Runner;
4+
5+
use Symfony\Component\HttpFoundation\Request;
6+
use Symfony\Component\HttpKernel\HttpKernelInterface;
7+
use Symfony\Component\HttpKernel\TerminableInterface;
8+
use Symfony\Component\Runtime\RunnerInterface;
9+
10+
/**
11+
* A runner for FrankenPHP.
12+
*
13+
* @author Kévin Dunglas <kevin@dunglas.dev>
14+
*/
15+
class FrankenPhpRunner implements RunnerInterface
16+
{
17+
public function __construct(
18+
private HttpKernelInterface $kernel,
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, &$sfRequest, &$sfResponse, $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+
$sfRequest = Request::createFromGlobals();
43+
$sfResponse = $this->kernel->handle($sfRequest);
44+
45+
$sfResponse->send();
46+
};
47+
48+
$loops = 0;
49+
do {
50+
$ret = \frankenphp_handle_request($handler);
51+
52+
if ($this->kernel instanceof TerminableInterface && $sfRequest && $sfResponse) {
53+
$this->kernel->terminate($sfRequest, $sfResponse);
54+
}
55+
56+
gc_collect_cycles();
57+
} while ($ret && (-1 === $this->loopMax || ++$loops < $this->loopMax));
58+
59+
return 0;
60+
}
61+
}
+41Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Symfony\Component\Runtime\Tests;
4+
5+
require_once __DIR__.'/frankenphp-function-mock.php';
6+
7+
use PHPUnit\Framework\TestCase;
8+
use Symfony\Component\HttpFoundation\Request;
9+
use Symfony\Component\HttpFoundation\Response;
10+
use Symfony\Component\HttpKernel\HttpKernelInterface;
11+
use Symfony\Component\HttpKernel\TerminableInterface;
12+
use Symfony\Component\Runtime\Runner\FrankenPhpRunner;
13+
14+
interface TestAppInterface extends HttpKernelInterface, TerminableInterface
15+
{
16+
}
17+
18+
/**
19+
* @author Kévin Dunglas <kevin@dunglas.fr>
20+
*/
21+
class FrankenPhpRunnerTest extends TestCase
22+
{
23+
public function testRun()
24+
{
25+
$application = $this->createMock(TestAppInterface::class);
26+
$application
27+
->expects($this->once())
28+
->method('handle')
29+
->willReturnCallback(function (Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response {
30+
$this->assertSame('bar', $request->server->get('FOO'));
31+
32+
return new Response();
33+
});
34+
$application->expects($this->once())->method('terminate');
35+
36+
$_SERVER['FOO'] = 'bar';
37+
38+
$runner = new FrankenPhpRunner($application, 500);
39+
$this->assertSame(0, $runner->run());
40+
}
41+
}
+26Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Symfony\Component\Runtime\Tests;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Symfony\Component\HttpKernel\HttpKernelInterface;
7+
use Symfony\Component\Runtime\FrankenPhpRuntime;
8+
use Symfony\Component\Runtime\Runner\FrankenPhpRunner;
9+
10+
/**
11+
* @author Kévin Dunglas <kevin@dunglas.dev>
12+
*/
13+
class FrankenPhpRuntimeTest extends TestCase
14+
{
15+
public function testGetRunner()
16+
{
17+
$application = $this->createStub(HttpKernelInterface::class);
18+
19+
$runtime = new FrankenPhpRuntime();
20+
$this->assertNotInstanceOf(FrankenPhpRunner::class, $runtime->getRunner(null));
21+
$this->assertNotInstanceOf(FrankenPhpRunner::class, $runtime->getRunner($application));
22+
23+
$_SERVER['FRANKENPHP_WORKER'] = 1;
24+
$this->assertInstanceOf(FrankenPhpRunner::class, $runtime->getRunner($application));
25+
}
26+
}
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
if (!function_exists('frankenphp_handle_request')) {
4+
function frankenphp_handle_request(callable $callable): bool
5+
{
6+
$callable();
7+
8+
return false;
9+
}
10+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.