Skip to content

Navigation Menu

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 2d7b9f4

Browse filesBrowse files
[Runtime] a new component to decouple applications from global state
1 parent 20bb3cb commit 2d7b9f4
Copy full SHA for 2d7b9f4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Dismiss banner

42 files changed

+1398
-1
lines changed

‎.gitattributes

Copy file name to clipboardExpand all lines: .gitattributes
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
/src/Symfony/Component/Mailer/Bridge export-ignore
44
/src/Symfony/Component/Messenger/Bridge export-ignore
55
/src/Symfony/Component/Notifier/Bridge export-ignore
6+
/src/Symfony/Component/Runtime export-ignore

‎.github/patch-types.php

Copy file name to clipboardExpand all lines: .github/patch-types.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
case false !== strpos($file, '/src/Symfony/Component/ErrorHandler/Tests/Fixtures/'):
3131
case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php'):
3232
case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php'):
33+
case false !== strpos($file, '/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php'):
3334
case false !== strpos($file, '/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectOuter.php'):
3435
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/NotLoadableClass.php'):
3536
continue 2;

‎composer.json

Copy file name to clipboardExpand all lines: composer.json
+6-1Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@
5252
"symfony/polyfill-mbstring": "~1.0",
5353
"symfony/polyfill-php73": "^1.11",
5454
"symfony/polyfill-php80": "^1.15",
55-
"symfony/polyfill-uuid": "^1.15"
55+
"symfony/polyfill-uuid": "^1.15",
56+
"symfony/runtime": "self.version"
5657
},
5758
"replace": {
5859
"symfony/asset": "self.version",
@@ -190,6 +191,10 @@
190191
"symfony/contracts": "2.3.x-dev"
191192
}
192193
}
194+
},
195+
{
196+
"type": "path",
197+
"url": "src/Symfony/Component/Runtime"
193198
}
194199
],
195200
"minimum-stability": "dev"

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/services.php
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
use Symfony\Component\HttpKernel\KernelEvents;
3939
use Symfony\Component\HttpKernel\KernelInterface;
4040
use Symfony\Component\HttpKernel\UriSigner;
41+
use Symfony\Component\Runtime\SymfonyRuntime;
4142
use Symfony\Component\String\LazyString;
4243
use Symfony\Component\String\Slugger\AsciiSlugger;
4344
use Symfony\Component\String\Slugger\SluggerInterface;
@@ -78,6 +79,7 @@ class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : []
7879
service('argument_resolver'),
7980
])
8081
->tag('container.hot_path')
82+
->tag('container.preload', ['class' => SymfonyRuntime::class])
8183
->alias(HttpKernelInterface::class, 'http_kernel')
8284

8385
->set('request_stack', RequestStack::class)
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/Tests export-ignore
2+
/phpunit.xml.dist export-ignore
3+
/.gitattributes export-ignore
4+
/.gitignore export-ignore
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
5.3.0
5+
-----
6+
7+
* Add the component
+149Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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\Component\Runtime;
13+
14+
use Symfony\Component\Runtime\Internal\BasicErrorHandler;
15+
use Symfony\Component\Runtime\Resolver\ClosureResolver;
16+
use Symfony\Component\Runtime\Resolver\DebugClosureResolver;
17+
use Symfony\Component\Runtime\Runner\ClosureRunner;
18+
19+
// Help opcache.preload discover always-needed symbols
20+
class_exists(ClosureResolver::class);
21+
22+
/**
23+
* A runtime to do bare-metal PHP without using superglobals.
24+
*
25+
* One option named "debug" is supported; it toggles displaying errors.
26+
*
27+
* The app-callable can declare arguments among either:
28+
* - "array $context" to get a local array similar to $_SERVER;
29+
* - "array $argv" to get the command line arguments when running on the CLI;
30+
* - "array $request" to get a local array with keys "query", "data", "files" and
31+
* "session", which map to $_GET, $_POST, $FILES and &$_SESSION respectively.
32+
*
33+
* It should return a Closure():int|string|null or an instance of RunnerInterface.
34+
*
35+
* In debug mode, the runtime registers a strict error handler
36+
* that throws exceptions when a PHP warning/notice is raised.
37+
*
38+
* @author Nicolas Grekas <p@tchwork.com>
39+
*/
40+
class GenericRuntime implements RuntimeInterface
41+
{
42+
private $debug;
43+
44+
public function __construct(array $options = [])
45+
{
46+
if ($this->debug = $options['debug'] ?? true) {
47+
$errorHandler = new BasicErrorHandler($this->debug);
48+
set_error_handler($errorHandler);
49+
}
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
public function getResolver(callable $callable): ResolverInterface
56+
{
57+
if (!$callable instanceof \Closure) {
58+
$callable = \Closure::fromCallable($callable);
59+
}
60+
61+
$function = new \ReflectionFunction($callable);
62+
$parameters = $function->getParameters();
63+
64+
$arguments = function () use ($parameters) {
65+
$arguments = [];
66+
67+
try {
68+
foreach ($parameters as $parameter) {
69+
$type = $parameter->getType();
70+
$arguments[] = $this->getArgument($parameter, $type instanceof \ReflectionNamedType ? $type->getName() : null);
71+
}
72+
} catch (\InvalidArgumentException $e) {
73+
if (!$parameter->isOptional()) {
74+
throw $e;
75+
}
76+
}
77+
78+
return $arguments;
79+
};
80+
81+
if ($this->debug) {
82+
return new DebugClosureResolver($callable, $arguments);
83+
}
84+
85+
return new ClosureResolver($callable, $arguments);
86+
}
87+
88+
/**
89+
* {@inheritdoc}
90+
*/
91+
public function getRunner(object $application): RunnerInterface
92+
{
93+
if ($application instanceof RunnerInterface) {
94+
return $application;
95+
}
96+
97+
if (!\is_callable($application)) {
98+
throw new \LogicException(sprintf('"%s" doesn\'t know how to handle apps of type "%s".', get_debug_type($this), get_debug_type($application)));
99+
}
100+
101+
if (!$application instanceof \Closure) {
102+
$application = \Closure::fromCallable($application);
103+
}
104+
105+
if ($this->debug && ($r = new \ReflectionFunction($application)) && $r->getNumberOfRequiredParameters()) {
106+
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()));
107+
}
108+
109+
return new ClosureRunner($application);
110+
}
111+
112+
/**
113+
* @return mixed
114+
*/
115+
protected function getArgument(\ReflectionParameter $parameter, ?string $type)
116+
{
117+
if ('array' === $type) {
118+
switch ($parameter->name) {
119+
case 'context':
120+
$context = $_SERVER;
121+
122+
if ($_ENV && !isset($_SERVER['PATH']) && !isset($_SERVER['Path'])) {
123+
$context += $_ENV;
124+
}
125+
126+
return $context;
127+
128+
case 'argv':
129+
return $_SERVER['argv'] ?? [];
130+
131+
case 'request':
132+
return [
133+
'query' => $_GET,
134+
'data' => $_POST,
135+
'files' => $_FILES,
136+
'session' => &$_SESSION,
137+
];
138+
}
139+
}
140+
141+
if (RuntimeInterface::class === $type) {
142+
return $this;
143+
}
144+
145+
$r = $parameter->getDeclaringFunction();
146+
147+
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)));
148+
}
149+
}
+53Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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\Component\Runtime\Internal;
13+
14+
/**
15+
* @author Nicolas Grekas <p@tchwork.com>
16+
*
17+
* @internal
18+
*/
19+
class BasicErrorHandler
20+
{
21+
public function __construct(bool $debug)
22+
{
23+
error_reporting(-1);
24+
25+
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
26+
ini_set('display_errors', $debug);
27+
} elseif (!filter_var(ini_get('log_errors'), \FILTER_VALIDATE_BOOLEAN) || ini_get('error_log')) {
28+
// CLI - display errors only if they're not already logged to STDERR
29+
ini_set('display_errors', 1);
30+
}
31+
32+
if (0 <= ini_get('zend.assertions')) {
33+
ini_set('zend.assertions', 1);
34+
ini_set('assert.active', $debug);
35+
ini_set('assert.bail', 0);
36+
ini_set('assert.warning', 0);
37+
ini_set('assert.exception', 1);
38+
}
39+
}
40+
41+
public function __invoke(int $type, string $message, string $file, int $line): bool
42+
{
43+
if ((\E_DEPRECATED | \E_USER_DEPRECATED) & $type) {
44+
return true;
45+
}
46+
47+
if ((error_reporting() | \E_ERROR | \E_RECOVERABLE_ERROR | \E_PARSE | \E_CORE_ERROR | \E_COMPILE_ERROR | \E_USER_ERROR) & $type) {
48+
throw new \ErrorException($message, 0, $type, $file, $line);
49+
}
50+
51+
return false;
52+
}
53+
}
+128Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
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\Component\Runtime\Internal;
13+
14+
use Composer\Composer;
15+
use Composer\EventDispatcher\EventSubscriberInterface;
16+
use Composer\Factory;
17+
use Composer\IO\IOInterface;
18+
use Composer\Plugin\PluginInterface;
19+
use Composer\Script\ScriptEvents;
20+
use Symfony\Component\Filesystem\Filesystem;
21+
use Symfony\Component\Runtime\RuntimeInterface;
22+
use Symfony\Component\Runtime\SymfonyRuntime;
23+
24+
/**
25+
* @author Nicolas Grekas <p@tchwork.com>
26+
*
27+
* @internal
28+
*/
29+
class ComposerPlugin implements PluginInterface, EventSubscriberInterface
30+
{
31+
/**
32+
* @var Composer
33+
*/
34+
private $composer;
35+
36+
/**
37+
* @var IOInterface
38+
*/
39+
private $io;
40+
41+
private static $activated = false;
42+
43+
public function activate(Composer $composer, IOInterface $io): void
44+
{
45+
self::$activated = true;
46+
$this->composer = $composer;
47+
$this->io = $io;
48+
}
49+
50+
public function deactivate(Composer $composer, IOInterface $io): void
51+
{
52+
self::$activated = false;
53+
}
54+
55+
public function uninstall(Composer $composer, IOInterface $io): void
56+
{
57+
@unlink($composer->getConfig()->get('vendor-dir').'/autoload_runtime.php');
58+
}
59+
60+
public function updateAutoloadFile(): void
61+
{
62+
$vendorDir = $this->composer->getConfig()->get('vendor-dir');
63+
64+
if (!is_file($autoloadFile = $vendorDir.'/autoload.php')
65+
|| false === $extra = $this->composer->getPackage()->getExtra()['runtime'] ?? []
66+
) {
67+
return;
68+
}
69+
70+
$fs = new Filesystem();
71+
$projectDir = \dirname(realpath(Factory::getComposerFile()));
72+
73+
if (null === $autoloadTemplate = $extra['autoload_template'] ?? null) {
74+
$autoloadTemplate = __DIR__.'/autoload_runtime.template';
75+
} else {
76+
if (!$fs->isAbsolutePath($autoloadTemplate)) {
77+
$autoloadTemplate = $projectDir.'/'.$autoloadTemplate;
78+
}
79+
80+
if (!is_file($autoloadTemplate)) {
81+
throw new \InvalidArgumentException(sprintf('File "%s" defined under "extra.runtime.autoload_template" in your composer.json file not found.', $this->composer->getPackage()->getExtra()['runtime']['autoload_template']));
82+
}
83+
}
84+
85+
$projectDir = $fs->makePathRelative($projectDir, $vendorDir);
86+
$nestingLevel = 0;
87+
88+
while (0 === strpos($projectDir, '../')) {
89+
++$nestingLevel;
90+
$projectDir = substr($projectDir, 3);
91+
}
92+
93+
if (!$nestingLevel) {
94+
$projectDir = '__'.'DIR__.'.var_export('/'.$projectDir, true);
95+
} else {
96+
$projectDir = 'dirname(__'."DIR__, $nestingLevel)".('' !== $projectDir ? var_export('/'.$projectDir, true) : '');
97+
}
98+
99+
$runtimeClass = $extra['class'] ?? SymfonyRuntime::class;
100+
101+
if (SymfonyRuntime::class !== $runtimeClass && !is_subclass_of($runtimeClass, RuntimeInterface::class)) {
102+
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));
103+
}
104+
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+
}
108+
109+
$code = strtr(file_get_contents($autoloadTemplate), [
110+
'%project_dir%' => $projectDir,
111+
'%runtime_class%' => var_export($runtimeClass, true),
112+
'%runtime_options%' => '['.substr(var_export($runtimeOptions, true), 7, -1)." 'project_dir' => {$projectDir},\n]",
113+
]);
114+
115+
file_put_contents(substr_replace($autoloadFile, '_runtime', -4, 0), $code);
116+
}
117+
118+
public static function getSubscribedEvents(): array
119+
{
120+
if (!self::$activated) {
121+
return [];
122+
}
123+
124+
return [
125+
ScriptEvents::POST_AUTOLOAD_DUMP => 'updateAutoloadFile',
126+
];
127+
}
128+
}

0 commit comments

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