Skip to content

Commit 94d121d

Browse files
[Runtime] make GenericRuntime ... generic
1 parent 49d23d4 commit 94d121d

19 files changed

+357
-59
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
use Symfony\Component\HttpKernel\KernelEvents;
3939
use Symfony\Component\HttpKernel\KernelInterface;
4040
use Symfony\Component\HttpKernel\UriSigner;
41+
use Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner;
42+
use Symfony\Component\Runtime\Runner\Symfony\ResponseRunner;
4143
use Symfony\Component\Runtime\SymfonyRuntime;
4244
use Symfony\Component\String\LazyString;
4345
use Symfony\Component\String\Slugger\AsciiSlugger;
@@ -79,6 +81,8 @@ class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : []
7981
service('argument_resolver'),
8082
])
8183
->tag('container.hot_path')
84+
->tag('container.preload', ['class' => HttpKernelRunner::class])
85+
->tag('container.preload', ['class' => ResponseRunner::class])
8286
->tag('container.preload', ['class' => SymfonyRuntime::class])
8387
->alias(HttpKernelInterface::class, 'http_kernel')
8488

src/Symfony/Component/Runtime/GenericRuntime.php

+83-21
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@ class_exists(ClosureResolver::class);
2222
/**
2323
* A runtime to do bare-metal PHP without using superglobals.
2424
*
25-
* One option named "debug" is supported; it toggles displaying errors
26-
* and defaults to the "APP_ENV" environment variable.
25+
* It supports the following options:
26+
* - "debug" toggles displaying errors and defaults
27+
* to the "APP_DEBUG" environment variable;
28+
* - "runtimes" maps types to a GenericRuntime implementation
29+
* that knows how to deal with each of them;
30+
* - "error_handler" defines the class to use to handle PHP errors.
2731
*
2832
* The app-callable can declare arguments among either:
2933
* - "array $context" to get a local array similar to $_SERVER;
@@ -42,42 +46,48 @@ class_exists(ClosureResolver::class);
4246
*/
4347
class GenericRuntime implements RuntimeInterface
4448
{
45-
private $debug;
49+
protected $options;
4650

4751
/**
4852
* @param array {
4953
* debug?: ?bool,
54+
* runtimes?: ?array,
55+
* error_handler?: string|false,
5056
* } $options
5157
*/
5258
public function __construct(array $options = [])
5359
{
54-
$this->debug = $options['debug'] ?? $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? true;
60+
$debug = $options['debug'] ?? $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? true;
5561

56-
if (!\is_bool($this->debug)) {
57-
$this->debug = filter_var($this->debug, \FILTER_VALIDATE_BOOLEAN);
62+
if (!\is_bool($debug)) {
63+
$debug = filter_var($debug, \FILTER_VALIDATE_BOOLEAN);
5864
}
5965

60-
if ($this->debug) {
66+
if ($debug) {
67+
umask(0000);
6168
$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '1';
62-
$errorHandler = new BasicErrorHandler($this->debug);
63-
set_error_handler($errorHandler);
69+
70+
if (false !== $errorHandler = ($options['error_handler'] ?? BasicErrorHandler::class)) {
71+
$errorHandler::register($debug);
72+
$options['error_handler'] = false;
73+
}
6474
} else {
6575
$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0';
6676
}
77+
78+
$this->options = $options;
6779
}
6880

6981
/**
7082
* {@inheritdoc}
7183
*/
72-
public function getResolver(callable $callable): ResolverInterface
84+
public function getResolver(callable $callable, \ReflectionFunction $reflector = null): ResolverInterface
7385
{
7486
if (!$callable instanceof \Closure) {
7587
$callable = \Closure::fromCallable($callable);
7688
}
7789

78-
$function = new \ReflectionFunction($callable);
79-
$parameters = $function->getParameters();
80-
90+
$parameters = ($reflector ?? new \ReflectionFunction($callable))->getParameters();
8191
$arguments = function () use ($parameters) {
8292
$arguments = [];
8393

@@ -95,7 +105,7 @@ public function getResolver(callable $callable): ResolverInterface
95105
return $arguments;
96106
};
97107

98-
if ($this->debug) {
108+
if ($_SERVER['APP_DEBUG']) {
99109
return new DebugClosureResolver($callable, $arguments);
100110
}
101111

@@ -115,15 +125,19 @@ public function getRunner(?object $application): RunnerInterface
115125
return $application;
116126
}
117127

118-
if (!\is_callable($application)) {
119-
throw new \LogicException(sprintf('"%s" doesn\'t know how to handle apps of type "%s".', get_debug_type($this), get_debug_type($application)));
120-
}
121-
122128
if (!$application instanceof \Closure) {
129+
if ($runtime = $this->resolveRuntime(\get_class($application))) {
130+
return $runtime->getRunner($application);
131+
}
132+
133+
if (!\is_callable($application)) {
134+
throw new \LogicException(sprintf('"%s" doesn\'t know how to handle apps of type "%s".', get_debug_type($this), get_debug_type($application)));
135+
}
136+
123137
$application = \Closure::fromCallable($application);
124138
}
125139

126-
if ($this->debug && ($r = new \ReflectionFunction($application)) && $r->getNumberOfRequiredParameters()) {
140+
if ($_SERVER['APP_DEBUG'] && ($r = new \ReflectionFunction($application)) && $r->getNumberOfRequiredParameters()) {
127141
throw new \ArgumentCountError(sprintf('Zero argument should be required by the runner callable, but at least one is in "%s" on line "%d.', $r->getFileName(), $r->getStartLine()));
128142
}
129143

@@ -163,8 +177,56 @@ protected function getArgument(\ReflectionParameter $parameter, ?string $type)
163177
return $this;
164178
}
165179

166-
$r = $parameter->getDeclaringFunction();
180+
if (!$runtime = $this->getRuntime($type)) {
181+
$r = $parameter->getDeclaringFunction();
182+
183+
throw new \InvalidArgumentException(sprintf('Cannot resolve argument "%s $%s" in "%s" on line "%d": "%s" supports only arguments "array $context", "array $argv" and "array $request", or a runtime named "Symfony\Runtime\%1$sRuntime".', $type, $parameter->name, $r->getFileName(), $r->getStartLine(), get_debug_type($this)));
184+
}
185+
186+
return $runtime->getArgument($parameter, $type);
187+
}
188+
189+
protected static function register(self $runtime): self
190+
{
191+
return $runtime;
192+
}
193+
194+
private function getRuntime(string $type): ?self
195+
{
196+
if (null === $runtime = ($this->options['runtimes'][$type] ?? null)) {
197+
$runtime = 'Symfony\Runtime\\'.$type.'Runtime';
198+
$runtime = class_exists($runtime) ? $runtime : $this->options['runtimes'][$type] = false;
199+
}
200+
201+
if (\is_string($runtime)) {
202+
$runtime = $runtime::register($this);
203+
}
204+
205+
if ($this === $runtime) {
206+
return null;
207+
}
208+
209+
return $runtime ?: null;
210+
}
211+
212+
private function resolveRuntime(string $class): ?self
213+
{
214+
if ($runtime = $this->getRuntime($class)) {
215+
return $runtime;
216+
}
217+
218+
foreach (class_parents($class) as $type) {
219+
if ($runtime = $this->getRuntime($type)) {
220+
return $runtime;
221+
}
222+
}
223+
224+
foreach (class_implements($class) as $type) {
225+
if ($runtime = $this->getRuntime($type)) {
226+
return $runtime;
227+
}
228+
}
167229

168-
throw new \InvalidArgumentException(sprintf('Cannot resolve argument "%s $%s" in "%s" on line "%d": "%s" supports only arguments "array $context", "array $argv" and "array $request".', $type, $parameter->name, $r->getFileName(), $r->getStartLine(), get_debug_type($this)));
230+
return null;
169231
}
170232
}

src/Symfony/Component/Runtime/Internal/BasicErrorHandler.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919
class BasicErrorHandler
2020
{
21-
public function __construct(bool $debug)
21+
public static function register(bool $debug): void
2222
{
2323
error_reporting(-1);
2424

@@ -32,10 +32,11 @@ public function __construct(bool $debug)
3232
if (0 <= ini_get('zend.assertions')) {
3333
ini_set('zend.assertions', 1);
3434
ini_set('assert.active', $debug);
35-
ini_set('assert.bail', 0);
3635
ini_set('assert.warning', 0);
3736
ini_set('assert.exception', 1);
3837
}
38+
39+
set_error_handler(new self());
3940
}
4041

4142
public function __invoke(int $type, string $message, string $file, int $line): bool

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

+2-4
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,12 @@ public function updateAutoloadFile(): void
102102
throw new \InvalidArgumentException(sprintf('Class "%s" listed under "extra.runtime.class" in your composer.json file '.(class_exists($runtimeClass) ? 'should implement "%s".' : 'not found.'), $runtimeClass, RuntimeInterface::class));
103103
}
104104

105-
if (!\is_array($runtimeOptions = $extra['options'] ?? [])) {
106-
throw new \InvalidArgumentException('The "extra.runtime.options" entry in your composer.json file must be an array.');
107-
}
105+
unset($extra['class'], $extra['autoload_template']);
108106

109107
$code = strtr(file_get_contents($autoloadTemplate), [
110108
'%project_dir%' => $projectDir,
111109
'%runtime_class%' => var_export($runtimeClass, true),
112-
'%runtime_options%' => '['.substr(var_export($runtimeOptions, true), 7, -1)." 'project_dir' => {$projectDir},\n]",
110+
'%runtime_options%' => '['.substr(var_export($extra, true), 7, -1)." 'project_dir' => {$projectDir},\n]",
113111
]);
114112

115113
file_put_contents(substr_replace($autoloadFile, '_runtime', -4, 0), $code);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Runtime\Symfony\Component\Console;
13+
14+
use Symfony\Component\Runtime\SymfonyRuntime;
15+
16+
/**
17+
* @internal
18+
*/
19+
class ApplicationRuntime extends SymfonyRuntime
20+
{
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Runtime\Symfony\Component\Console\Command;
13+
14+
use Symfony\Component\Runtime\SymfonyRuntime;
15+
16+
/**
17+
* @internal
18+
*/
19+
class CommandRuntime extends SymfonyRuntime
20+
{
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Runtime\Symfony\Component\Console\Input;
13+
14+
use Symfony\Component\Runtime\SymfonyRuntime;
15+
16+
/**
17+
* @internal
18+
*/
19+
class InputInterfaceRuntime extends SymfonyRuntime
20+
{
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Runtime\Symfony\Component\Console\Output;
13+
14+
use Symfony\Component\Runtime\SymfonyRuntime;
15+
16+
/**
17+
* @internal
18+
*/
19+
class OutputInterfaceRuntime extends SymfonyRuntime
20+
{
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Runtime\Symfony\Component\HttpFoundation;
13+
14+
use Symfony\Component\Runtime\SymfonyRuntime;
15+
16+
/**
17+
* @internal
18+
*/
19+
class RequestRuntime extends SymfonyRuntime
20+
{
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Runtime\Symfony\Component\HttpFoundation;
13+
14+
use Symfony\Component\Runtime\SymfonyRuntime;
15+
16+
/**
17+
* @internal
18+
*/
19+
class ResponseRuntime extends SymfonyRuntime
20+
{
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Runtime\Symfony\Component\HttKernel;
13+
14+
use Symfony\Component\Runtime\SymfonyRuntime;
15+
16+
/**
17+
* @internal
18+
*/
19+
class HttpKernelInterfaceRuntime extends SymfonyRuntime
20+
{
21+
}

0 commit comments

Comments
 (0)