Skip to content

Commit e26da8a

Browse files
[Runtime] a new component to decouple apps from global state
1 parent 4e42149 commit e26da8a

Some content is hidden

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

45 files changed

+1397
-17
lines changed

.appveyor.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,8 @@ install:
4949
- git config --global user.email ""
5050
- git config --global user.name "Symfony"
5151
- FOR /F "tokens=* USEBACKQ" %%F IN (`bash -c "grep branch-version composer.json | grep -o '[0-9.x]*'"`) DO (SET SYMFONY_VERSION=%%F)
52-
- php .github/build-packages.php "HEAD^" %SYMFONY_VERSION% src\Symfony\Bridge\PhpUnit src\Symfony\Contracts
52+
- php .github/build-packages.php HEAD^ %SYMFONY_VERSION% src\Symfony\Bridge\PhpUnit
5353
- SET "SYMFONY_REQUIRE=>=%SYMFONY_VERSION%"
54-
- SET COMPOSER_ROOT_VERSION=%SYMFONY_VERSION%.x-dev
5554
- php composer.phar update --no-progress --ansi
5655
- php phpunit install
5756

.github/build-packages.php

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,6 @@
66
}
77
chdir(dirname(__DIR__));
88

9-
$json = ltrim(file_get_contents('composer.json'));
10-
if ($json !== $package = preg_replace('/\n "repositories": \[\n.*?\n \],/s', '', $json)) {
11-
file_put_contents('composer.json', $package);
12-
}
13-
149
$dirs = $_SERVER['argv'];
1510
array_shift($dirs);
1611
$mergeBase = trim(shell_exec(sprintf('git merge-base "%s" HEAD', array_shift($dirs))));
@@ -74,10 +69,12 @@
7469
exit(1);
7570
}
7671

77-
$package->repositories = array(array(
72+
$package->repositories[] = array(
7873
'type' => 'composer',
7974
'url' => 'file://'.str_replace(DIRECTORY_SEPARATOR, '/', dirname(__DIR__)).'/',
80-
));
75+
);
76+
77+
$json = preg_replace('/\n "repositories": \[\n.*?\n \],/s', '', $json);
8178
$json = rtrim(json_encode(array('repositories' => $package->repositories), $flags), "\n}").','.substr($json, 1);
8279
file_put_contents('composer.json', $json);
8380
}

.github/patch-types.php

Lines 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'):

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ jobs:
155155
run: |
156156
echo "::group::composer update"
157157
composer require --dev --no-update mongodb/mongodb:@stable
158-
composer update --no-progress --no-suggest --ansi
158+
composer update --no-progress --ansi
159159
echo "::endgroup::"
160160
echo "::group::install phpunit"
161161
./phpunit install

.travis.yml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ install:
185185
export SYMFONY_VERSION=$(grep branch-version composer.json | grep -o '[0-9.x]*')
186186
187187
if [[ ! $deps ]]; then
188-
php .github/build-packages.php HEAD^ $SYMFONY_VERSION src/Symfony/Bridge/PhpUnit src/Symfony/Contracts
188+
php .github/build-packages.php HEAD^ $SYMFONY_VERSION src/Symfony/Bridge/PhpUnit
189189
else
190190
export SYMFONY_DEPRECATIONS_HELPER=weak &&
191191
cp composer.json composer.json.orig &&
@@ -228,7 +228,6 @@ install:
228228
# Legacy tests are skipped when deps=high and when the current branch version has not the same major version number as the next one
229229
[[ $deps = high && ${SYMFONY_VERSION%.*} != $(git ls-remote -q --heads | cut -f2 | grep -FA1 /$SYMFONY_VERSION | tail -n 1 | grep -o '[0-9]*' | head -n 1) ]] && export LEGACY=,legacy
230230
231-
export COMPOSER_ROOT_VERSION=$SYMFONY_VERSION.x-dev
232231
if [[ $deps ]]; then mv composer.json.phpunit composer.json; fi
233232
234233
- |
@@ -268,7 +267,6 @@ install:
268267
SYMFONY_VERSION=$(echo $SYMFONY_VERSION | awk '{print $1 - 1}')
269268
echo -e "\\n\\e[33;1mChecking out Symfony $SYMFONY_VERSION and running tests with patched components as deps\\e[0m"
270269
export SYMFONY_REQUIRE=">=$SYMFONY_VERSION"
271-
export COMPOSER_ROOT_VERSION=$SYMFONY_VERSION.x-dev
272270
git fetch --depth=2 origin $SYMFONY_VERSION
273271
git checkout -m FETCH_HEAD
274272
COMPONENTS=$(echo "$COMPONENTS" | xargs dirname | xargs -n1 -I{} bash -c "[ -e '{}/phpunit.xml.dist' ] && echo '{}'" | sort)
@@ -287,8 +285,6 @@ install:
287285
else
288286
if [[ $PHP = 7.4* ]]; then
289287
# add return types before running the test suite
290-
rm src/Symfony/Contracts -Rf && mv vendor/symfony/contracts src/Symfony/Contracts
291-
ln -sd $(realpath src/Symfony/Contracts) vendor/symfony/contracts
292288
sed -i 's/"\*\*\/Tests\/"//' composer.json
293289
composer install --optimize-autoloader
294290
SYMFONY_PATCH_TYPE_DECLARATIONS=force=object php .github/patch-types.php

composer.json

Lines 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

Lines 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)
Lines 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
Lines 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
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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\ResolvedApp\ClosureResolved;
16+
use Symfony\Component\Runtime\ResolvedApp\ScalarResolved;
17+
use Symfony\Component\Runtime\StartedApp\ClosureStarted;
18+
19+
// Help opcache.preload discover always-needed symbols
20+
class_exists(ClosureResolved::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 BaseRuntime 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(\Closure $app): ResolvedAppInterface
56+
{
57+
$arguments = [];
58+
$function = new \ReflectionFunction($app);
59+
60+
try {
61+
foreach ($function->getParameters() as $parameter) {
62+
$arguments[] = $this->getArgument($parameter, $parameter->getType());
63+
}
64+
} catch (\InvalidArgumentException $e) {
65+
if (!$parameter->isOptional()) {
66+
throw $e;
67+
}
68+
}
69+
70+
$returnType = $function->getReturnType();
71+
72+
switch ($returnType instanceof \ReflectionNamedType ? $returnType->getName() : '') {
73+
case 'string':
74+
return new ScalarResolved(static function () use ($app, $arguments): int {
75+
echo $app(...$arguments);
76+
77+
return 0;
78+
});
79+
80+
case 'int':
81+
case 'void':
82+
return new ScalarResolved(static function () use ($app, $arguments): int {
83+
return $app(...$arguments) ?? 0;
84+
});
85+
}
86+
87+
return new ClosureResolved($app, $arguments);
88+
}
89+
90+
public function start(object $app): StartedAppInterface
91+
{
92+
if (!$app instanceof \Closure) {
93+
throw new \LogicException(sprintf('"%s" doesn\'t know how to handle apps of type "%s".', get_debug_type($this), get_debug_type($app)));
94+
}
95+
96+
if ($this->debug && (new \ReflectionFunction($app))->getNumberOfRequiredParameters()) {
97+
throw new \ArgumentCountError('Zero argument should be required by the closure returned by the app, but at least one is.');
98+
}
99+
100+
return new ClosureStarted($app);
101+
}
102+
103+
/**
104+
* @return mixed
105+
*/
106+
protected function getArgument(\ReflectionParameter $parameter, ?\ReflectionType $type)
107+
{
108+
$type = $type instanceof \ReflectionNamedType ? $type->getName() : '';
109+
110+
if (RuntimeInterface::class === $type) {
111+
return $this;
112+
}
113+
114+
if ('array' !== $type) {
115+
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)));
116+
}
117+
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+
throw new \InvalidArgumentException(sprintf('Cannot resolve array argument "$%s": "%s" supports only arguments "$context", "$argv" and "$request".', $parameter->name, get_debug_type($this)));
141+
}
142+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy