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 97780e8

Browse filesBrowse files
[Runtime] a new component to decouple applications from global state
1 parent 17eaad2 commit 97780e8
Copy full SHA for 97780e8

Some content is hidden

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

41 files changed

+1391
-2
lines changed

‎.github/patch-types.php

Copy file name to clipboardExpand all lines: .github/patch-types.php
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
foreach ($loader->getClassMap() as $class => $file) {
1515
switch (true) {
16-
case false !== strpos(realpath($file), '/vendor/'):
16+
case false !== strpos($file = realpath($file), '/vendor/'):
1717
case false !== strpos($file, '/src/Symfony/Bridge/PhpUnit/'):
1818
case false !== strpos($file, '/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Article.php'):
1919
case false !== strpos($file, '/src/Symfony/Component/Config/Tests/Fixtures/BadFileName.php'):
@@ -36,6 +36,7 @@
3636
case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php'):
3737
case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Php80Dummy.php'):
3838
case false !== strpos($file, '/src/Symfony/Component/Routing/Tests/Fixtures/AttributeFixtures'):
39+
case false !== strpos($file, '/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php'):
3940
case false !== strpos($file, '/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectOuter.php'):
4041
case false !== strpos($file, '/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/'):
4142
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/LotsOfAttributes.php'):

‎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
@@ -36,7 +36,8 @@
3636
"symfony/polyfill-mbstring": "~1.0",
3737
"symfony/polyfill-php73": "^1.11",
3838
"symfony/polyfill-php80": "^1.15",
39-
"symfony/polyfill-uuid": "^1.15"
39+
"symfony/polyfill-uuid": "^1.15",
40+
"symfony/runtime": "self.version"
4041
},
4142
"replace": {
4243
"symfony/asset": "self.version",
@@ -172,6 +173,10 @@
172173
{
173174
"type": "path",
174175
"url": "src/Symfony/Contracts"
176+
},
177+
{
178+
"type": "path",
179+
"url": "src/Symfony/Component/Runtime"
175180
}
176181
],
177182
"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+
* added the component
+146Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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\ResolvedCallable\ResolvedCallable;
16+
use Symfony\Component\Runtime\ResolvedCallable\ResolvedScalar;
17+
use Symfony\Component\Runtime\StartedApplication\StartedClosure;
18+
19+
// Help opcache.preload discover always-needed symbols
20+
class_exists(ResolvedCallable::class);
21+
class_exists(BasicErrorHandler::class);
22+
23+
/**
24+
* A runtime to do bare-metal PHP without using superglobals.
25+
*
26+
* One option named "debug" is supported; it toggles displaying errors.
27+
*
28+
* The app-closure returned by the entry script must return either:
29+
* - "string" to echo the response content, or
30+
* - "int" to set the exit status code.
31+
*
32+
* The app-closure can declare arguments among either:
33+
* - "array $context" to get a local array similar to $_SERVER;
34+
* - "array $argv" to get the command line arguments when running on the CLI;
35+
* - "array $request" to get a local array with keys "query", "data", "files" and
36+
* "session", which map to $_GET, $_POST, $FILES and &$_SESSION respectively.
37+
*
38+
* The runtime sets up a strict error handler that throws
39+
* exceptions when a PHP warning/notice is raised.
40+
*
41+
* @author Nicolas Grekas <p@tchwork.com>
42+
*/
43+
class GenericRuntime implements RuntimeInterface
44+
{
45+
private $debug;
46+
47+
public function __construct(array $options = [])
48+
{
49+
$this->debug = $options['debug'] ?? true;
50+
$errorHandler = new BasicErrorHandler($this->debug);
51+
set_error_handler($errorHandler);
52+
set_exception_handler([$errorHandler, 'handleException']);
53+
}
54+
55+
public function resolve(callable $callable): ResolvedCallableInterface
56+
{
57+
if (!$callable instanceof \Closure) {
58+
$callable = \Closure::fromCallable($callable);
59+
}
60+
61+
$arguments = [];
62+
$function = new \ReflectionFunction($callable);
63+
64+
try {
65+
foreach ($function->getParameters() as $parameter) {
66+
$arguments[] = $this->getArgument($parameter, $parameter->getType());
67+
}
68+
} catch (\InvalidArgumentException $e) {
69+
if (!$parameter->isOptional()) {
70+
throw $e;
71+
}
72+
}
73+
74+
$returnType = $function->getReturnType();
75+
76+
switch ($returnType instanceof \ReflectionNamedType ? $returnType->getName() : '') {
77+
case 'string':
78+
return new ResolvedScalar(static function () use ($callable, $arguments): int {
79+
echo $callable(...$arguments);
80+
81+
return 0;
82+
});
83+
84+
case 'int':
85+
case 'void':
86+
return new ResolvedScalar(static function () use ($callable, $arguments): int {
87+
return $callable(...$arguments) ?? 0;
88+
});
89+
}
90+
91+
return new ResolvedCallable($callable, $arguments);
92+
}
93+
94+
public function start(object $application): StartedApplicationInterface
95+
{
96+
if (!$application instanceof \Closure) {
97+
throw new \LogicException(sprintf('"%s" doesn\'t know how to handle apps of type "%s".', get_debug_type($this), get_debug_type($application)));
98+
}
99+
100+
if ($this->debug && (new \ReflectionFunction($application))->getNumberOfRequiredParameters()) {
101+
throw new \ArgumentCountError('Zero argument should be required by the closure returned by the app, but at least one is.');
102+
}
103+
104+
return new StartedClosure($application);
105+
}
106+
107+
/**
108+
* @return mixed
109+
*/
110+
protected function getArgument(\ReflectionParameter $parameter, ?\ReflectionType $type)
111+
{
112+
$type = $type instanceof \ReflectionNamedType ? $type->getName() : '';
113+
114+
if (RuntimeInterface::class === $type) {
115+
return $this;
116+
}
117+
118+
if ('array' !== $type) {
119+
throw new \InvalidArgumentException(sprintf('Cannot resolve argument "%s $%s": "%s" supports only arguments "$context", "$argv" and "$request" with type "array".', $type, $parameter->name, get_debug_type($this)));
120+
}
121+
122+
switch ($parameter->name) {
123+
case 'context':
124+
$context = $_SERVER;
125+
126+
if ($_ENV && !isset($_SERVER['PATH']) && !isset($_SERVER['Path'])) {
127+
$context += $_ENV;
128+
}
129+
130+
return $context;
131+
132+
case 'argv':
133+
return $_SERVER['argv'] ?? [];
134+
135+
case 'request':
136+
return [
137+
'query' => $_GET,
138+
'data' => $_POST,
139+
'files' => $_FILES,
140+
'session' => &$_SESSION,
141+
];
142+
}
143+
144+
throw new \InvalidArgumentException(sprintf('Cannot resolve array argument "$%s": "%s" supports only arguments "$context", "$argv" and "$request".', $parameter->name, get_debug_type($this)));
145+
}
146+
}
+58Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
54+
public function handleException(\Throwable $e): void
55+
{
56+
echo "<pre>\n$e\n</pre>\n";
57+
}
58+
}
+98Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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+
22+
/**
23+
* @author Nicolas Grekas <p@tchwork.com>
24+
*
25+
* @internal
26+
*/
27+
class ComposerPlugin implements PluginInterface, EventSubscriberInterface
28+
{
29+
/**
30+
* @var Composer
31+
*/
32+
private $composer;
33+
34+
/**
35+
* @var IOInterface
36+
*/
37+
private $io;
38+
39+
private static $activated = false;
40+
41+
public function activate(Composer $composer, IOInterface $io): void
42+
{
43+
self::$activated = true;
44+
$this->composer = $composer;
45+
$this->io = $io;
46+
}
47+
48+
public function deactivate(Composer $composer, IOInterface $io): void
49+
{
50+
self::$activated = false;
51+
}
52+
53+
public function uninstall(Composer $composer, IOInterface $io): void
54+
{
55+
@unlink($composer->getConfig()->get('vendor-dir').'/autoload_runtime.php');
56+
}
57+
58+
public function updateAutoloadFile(): void
59+
{
60+
$vendorDir = $this->composer->getConfig()->get('vendor-dir');
61+
$autoloadFile = $vendorDir.'/autoload.php';
62+
63+
if (!file_exists($autoloadFile)) {
64+
return;
65+
}
66+
67+
$projectDir = (new Filesystem())->makePathRelative(\dirname(realpath(Factory::getComposerFile())), $vendorDir);
68+
$nestingLevel = 0;
69+
70+
while (0 === strpos($projectDir, '../')) {
71+
++$nestingLevel;
72+
$projectDir = substr($projectDir, 3);
73+
}
74+
75+
if (!$nestingLevel) {
76+
$projectDir = '__'.'DIR__.'.var_export('/'.$projectDir, true);
77+
} else {
78+
$projectDir = 'dirname(__'."DIR__, $nestingLevel)".('' !== $projectDir ? var_export('/'.$projectDir, true) : '');
79+
}
80+
81+
$code = strtr(file_get_contents(__DIR__.'/autoload_runtime.template'), [
82+
'%project_dir%' => $projectDir,
83+
]);
84+
85+
file_put_contents(substr_replace($autoloadFile, '_runtime', -4, 0), $code);
86+
}
87+
88+
public static function getSubscribedEvents(): array
89+
{
90+
if (!self::$activated) {
91+
return [];
92+
}
93+
94+
return [
95+
ScriptEvents::POST_AUTOLOAD_DUMP => 'updateAutoloadFile',
96+
];
97+
}
98+
}
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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+
* @internal class that should be loaded only when symfony/dotenv is not installed
16+
*/
17+
class MissingDotenv
18+
{
19+
}

0 commit comments

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