[Runtime] a new component to decouple applications from global state
This commit is contained in:
parent
fc016ddd92
commit
61b32ab2a3
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -3,3 +3,4 @@
|
||||
/src/Symfony/Component/Mailer/Bridge export-ignore
|
||||
/src/Symfony/Component/Messenger/Bridge export-ignore
|
||||
/src/Symfony/Component/Notifier/Bridge export-ignore
|
||||
/src/Symfony/Component/Runtime export-ignore
|
||||
|
1
.github/patch-types.php
vendored
1
.github/patch-types.php
vendored
@ -30,6 +30,7 @@ foreach ($loader->getClassMap() as $class => $file) {
|
||||
case false !== strpos($file, '/src/Symfony/Component/ErrorHandler/Tests/Fixtures/'):
|
||||
case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php'):
|
||||
case false !== strpos($file, '/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ParentDummy.php'):
|
||||
case false !== strpos($file, '/src/Symfony/Component/Runtime/Internal/ComposerPlugin.php'):
|
||||
case false !== strpos($file, '/src/Symfony/Component/Serializer/Tests/Normalizer/Features/ObjectOuter.php'):
|
||||
case false !== strpos($file, '/src/Symfony/Component/VarDumper/Tests/Fixtures/NotLoadableClass.php'):
|
||||
continue 2;
|
||||
|
@ -52,7 +52,8 @@
|
||||
"symfony/polyfill-mbstring": "~1.0",
|
||||
"symfony/polyfill-php73": "^1.11",
|
||||
"symfony/polyfill-php80": "^1.15",
|
||||
"symfony/polyfill-uuid": "^1.15"
|
||||
"symfony/polyfill-uuid": "^1.15",
|
||||
"symfony/runtime": "self.version"
|
||||
},
|
||||
"replace": {
|
||||
"symfony/asset": "self.version",
|
||||
@ -193,6 +194,10 @@
|
||||
"symfony/contracts": "2.4.x-dev"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "path",
|
||||
"url": "src/Symfony/Component/Runtime"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "dev"
|
||||
|
@ -38,6 +38,7 @@ use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Symfony\Component\HttpKernel\UriSigner;
|
||||
use Symfony\Component\Runtime\SymfonyRuntime;
|
||||
use Symfony\Component\String\LazyString;
|
||||
use Symfony\Component\String\Slugger\AsciiSlugger;
|
||||
use Symfony\Component\String\Slugger\SluggerInterface;
|
||||
@ -78,6 +79,7 @@ return static function (ContainerConfigurator $container) {
|
||||
service('argument_resolver'),
|
||||
])
|
||||
->tag('container.hot_path')
|
||||
->tag('container.preload', ['class' => SymfonyRuntime::class])
|
||||
->alias(HttpKernelInterface::class, 'http_kernel')
|
||||
|
||||
->set('request_stack', RequestStack::class)
|
||||
|
4
src/Symfony/Component/Runtime/.gitattributes
vendored
Normal file
4
src/Symfony/Component/Runtime/.gitattributes
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/Tests export-ignore
|
||||
/phpunit.xml.dist export-ignore
|
||||
/.gitattributes export-ignore
|
||||
/.gitignore export-ignore
|
3
src/Symfony/Component/Runtime/.gitignore
vendored
Normal file
3
src/Symfony/Component/Runtime/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
7
src/Symfony/Component/Runtime/CHANGELOG.md
Normal file
7
src/Symfony/Component/Runtime/CHANGELOG.md
Normal file
@ -0,0 +1,7 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
5.3.0
|
||||
-----
|
||||
|
||||
* Add the component
|
170
src/Symfony/Component/Runtime/GenericRuntime.php
Normal file
170
src/Symfony/Component/Runtime/GenericRuntime.php
Normal file
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Runtime;
|
||||
|
||||
use Symfony\Component\Runtime\Internal\BasicErrorHandler;
|
||||
use Symfony\Component\Runtime\Resolver\ClosureResolver;
|
||||
use Symfony\Component\Runtime\Resolver\DebugClosureResolver;
|
||||
use Symfony\Component\Runtime\Runner\ClosureRunner;
|
||||
|
||||
// Help opcache.preload discover always-needed symbols
|
||||
class_exists(ClosureResolver::class);
|
||||
|
||||
/**
|
||||
* A runtime to do bare-metal PHP without using superglobals.
|
||||
*
|
||||
* One option named "debug" is supported; it toggles displaying errors
|
||||
* and defaults to the "APP_ENV" environment variable.
|
||||
*
|
||||
* The app-callable can declare arguments among either:
|
||||
* - "array $context" to get a local array similar to $_SERVER;
|
||||
* - "array $argv" to get the command line arguments when running on the CLI;
|
||||
* - "array $request" to get a local array with keys "query", "body", "files" and
|
||||
* "session", which map to $_GET, $_POST, $FILES and &$_SESSION respectively.
|
||||
*
|
||||
* It should return a Closure():int|string|null or an instance of RunnerInterface.
|
||||
*
|
||||
* In debug mode, the runtime registers a strict error handler
|
||||
* that throws exceptions when a PHP warning/notice is raised.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @experimental in 5.3
|
||||
*/
|
||||
class GenericRuntime implements RuntimeInterface
|
||||
{
|
||||
private $debug;
|
||||
|
||||
/**
|
||||
* @param array {
|
||||
* debug?: ?bool,
|
||||
* } $options
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->debug = $options['debug'] ?? $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? true;
|
||||
|
||||
if (!\is_bool($this->debug)) {
|
||||
$this->debug = filter_var($this->debug, \FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
if ($this->debug) {
|
||||
$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '1';
|
||||
$errorHandler = new BasicErrorHandler($this->debug);
|
||||
set_error_handler($errorHandler);
|
||||
} else {
|
||||
$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getResolver(callable $callable): ResolverInterface
|
||||
{
|
||||
if (!$callable instanceof \Closure) {
|
||||
$callable = \Closure::fromCallable($callable);
|
||||
}
|
||||
|
||||
$function = new \ReflectionFunction($callable);
|
||||
$parameters = $function->getParameters();
|
||||
|
||||
$arguments = function () use ($parameters) {
|
||||
$arguments = [];
|
||||
|
||||
try {
|
||||
foreach ($parameters as $parameter) {
|
||||
$type = $parameter->getType();
|
||||
$arguments[] = $this->getArgument($parameter, $type instanceof \ReflectionNamedType ? $type->getName() : null);
|
||||
}
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
if (!$parameter->isOptional()) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
};
|
||||
|
||||
if ($this->debug) {
|
||||
return new DebugClosureResolver($callable, $arguments);
|
||||
}
|
||||
|
||||
return new ClosureResolver($callable, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRunner(?object $application): RunnerInterface
|
||||
{
|
||||
if (null === $application) {
|
||||
$application = static function () { return 0; };
|
||||
}
|
||||
|
||||
if ($application instanceof RunnerInterface) {
|
||||
return $application;
|
||||
}
|
||||
|
||||
if (!\is_callable($application)) {
|
||||
throw new \LogicException(sprintf('"%s" doesn\'t know how to handle apps of type "%s".', get_debug_type($this), get_debug_type($application)));
|
||||
}
|
||||
|
||||
if (!$application instanceof \Closure) {
|
||||
$application = \Closure::fromCallable($application);
|
||||
}
|
||||
|
||||
if ($this->debug && ($r = new \ReflectionFunction($application)) && $r->getNumberOfRequiredParameters()) {
|
||||
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()));
|
||||
}
|
||||
|
||||
return new ClosureRunner($application);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getArgument(\ReflectionParameter $parameter, ?string $type)
|
||||
{
|
||||
if ('array' === $type) {
|
||||
switch ($parameter->name) {
|
||||
case 'context':
|
||||
$context = $_SERVER;
|
||||
|
||||
if ($_ENV && !isset($_SERVER['PATH']) && !isset($_SERVER['Path'])) {
|
||||
$context += $_ENV;
|
||||
}
|
||||
|
||||
return $context;
|
||||
|
||||
case 'argv':
|
||||
return $_SERVER['argv'] ?? [];
|
||||
|
||||
case 'request':
|
||||
return [
|
||||
'query' => $_GET,
|
||||
'body' => $_POST,
|
||||
'files' => $_FILES,
|
||||
'session' => &$_SESSION,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (RuntimeInterface::class === $type) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$r = $parameter->getDeclaringFunction();
|
||||
|
||||
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)));
|
||||
}
|
||||
}
|
53
src/Symfony/Component/Runtime/Internal/BasicErrorHandler.php
Normal file
53
src/Symfony/Component/Runtime/Internal/BasicErrorHandler.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Runtime\Internal;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BasicErrorHandler
|
||||
{
|
||||
public function __construct(bool $debug)
|
||||
{
|
||||
error_reporting(-1);
|
||||
|
||||
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
|
||||
ini_set('display_errors', $debug);
|
||||
} elseif (!filter_var(ini_get('log_errors'), \FILTER_VALIDATE_BOOLEAN) || ini_get('error_log')) {
|
||||
// CLI - display errors only if they're not already logged to STDERR
|
||||
ini_set('display_errors', 1);
|
||||
}
|
||||
|
||||
if (0 <= ini_get('zend.assertions')) {
|
||||
ini_set('zend.assertions', 1);
|
||||
ini_set('assert.active', $debug);
|
||||
ini_set('assert.bail', 0);
|
||||
ini_set('assert.warning', 0);
|
||||
ini_set('assert.exception', 1);
|
||||
}
|
||||
}
|
||||
|
||||
public function __invoke(int $type, string $message, string $file, int $line): bool
|
||||
{
|
||||
if ((\E_DEPRECATED | \E_USER_DEPRECATED) & $type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((error_reporting() | \E_ERROR | \E_RECOVERABLE_ERROR | \E_PARSE | \E_CORE_ERROR | \E_COMPILE_ERROR | \E_USER_ERROR) & $type) {
|
||||
throw new \ErrorException($message, 0, $type, $file, $line);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
128
src/Symfony/Component/Runtime/Internal/ComposerPlugin.php
Normal file
128
src/Symfony/Component/Runtime/Internal/ComposerPlugin.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Runtime\Internal;
|
||||
|
||||
use Composer\Composer;
|
||||
use Composer\EventDispatcher\EventSubscriberInterface;
|
||||
use Composer\Factory;
|
||||
use Composer\IO\IOInterface;
|
||||
use Composer\Plugin\PluginInterface;
|
||||
use Composer\Script\ScriptEvents;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Runtime\RuntimeInterface;
|
||||
use Symfony\Component\Runtime\SymfonyRuntime;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ComposerPlugin implements PluginInterface, EventSubscriberInterface
|
||||
{
|
||||
/**
|
||||
* @var Composer
|
||||
*/
|
||||
private $composer;
|
||||
|
||||
/**
|
||||
* @var IOInterface
|
||||
*/
|
||||
private $io;
|
||||
|
||||
private static $activated = false;
|
||||
|
||||
public function activate(Composer $composer, IOInterface $io): void
|
||||
{
|
||||
self::$activated = true;
|
||||
$this->composer = $composer;
|
||||
$this->io = $io;
|
||||
}
|
||||
|
||||
public function deactivate(Composer $composer, IOInterface $io): void
|
||||
{
|
||||
self::$activated = false;
|
||||
}
|
||||
|
||||
public function uninstall(Composer $composer, IOInterface $io): void
|
||||
{
|
||||
@unlink($composer->getConfig()->get('vendor-dir').'/autoload_runtime.php');
|
||||
}
|
||||
|
||||
public function updateAutoloadFile(): void
|
||||
{
|
||||
$vendorDir = $this->composer->getConfig()->get('vendor-dir');
|
||||
|
||||
if (!is_file($autoloadFile = $vendorDir.'/autoload.php')
|
||||
|| false === $extra = $this->composer->getPackage()->getExtra()['runtime'] ?? []
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fs = new Filesystem();
|
||||
$projectDir = \dirname(realpath(Factory::getComposerFile()));
|
||||
|
||||
if (null === $autoloadTemplate = $extra['autoload_template'] ?? null) {
|
||||
$autoloadTemplate = __DIR__.'/autoload_runtime.template';
|
||||
} else {
|
||||
if (!$fs->isAbsolutePath($autoloadTemplate)) {
|
||||
$autoloadTemplate = $projectDir.'/'.$autoloadTemplate;
|
||||
}
|
||||
|
||||
if (!is_file($autoloadTemplate)) {
|
||||
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']));
|
||||
}
|
||||
}
|
||||
|
||||
$projectDir = $fs->makePathRelative($projectDir, $vendorDir);
|
||||
$nestingLevel = 0;
|
||||
|
||||
while (0 === strpos($projectDir, '../')) {
|
||||
++$nestingLevel;
|
||||
$projectDir = substr($projectDir, 3);
|
||||
}
|
||||
|
||||
if (!$nestingLevel) {
|
||||
$projectDir = '__'.'DIR__.'.var_export('/'.$projectDir, true);
|
||||
} else {
|
||||
$projectDir = 'dirname(__'."DIR__, $nestingLevel)".('' !== $projectDir ? var_export('/'.$projectDir, true) : '');
|
||||
}
|
||||
|
||||
$runtimeClass = $extra['class'] ?? SymfonyRuntime::class;
|
||||
|
||||
if (SymfonyRuntime::class !== $runtimeClass && !is_subclass_of($runtimeClass, RuntimeInterface::class)) {
|
||||
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));
|
||||
}
|
||||
|
||||
if (!\is_array($runtimeOptions = $extra['options'] ?? [])) {
|
||||
throw new \InvalidArgumentException('The "extra.runtime.options" entry in your composer.json file must be an array.');
|
||||
}
|
||||
|
||||
$code = strtr(file_get_contents($autoloadTemplate), [
|
||||
'%project_dir%' => $projectDir,
|
||||
'%runtime_class%' => var_export($runtimeClass, true),
|
||||
'%runtime_options%' => '['.substr(var_export($runtimeOptions, true), 7, -1)." 'project_dir' => {$projectDir},\n]",
|
||||
]);
|
||||
|
||||
file_put_contents(substr_replace($autoloadFile, '_runtime', -4, 0), $code);
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
if (!self::$activated) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
ScriptEvents::POST_AUTOLOAD_DUMP => 'updateAutoloadFile',
|
||||
];
|
||||
}
|
||||
}
|
19
src/Symfony/Component/Runtime/Internal/MissingDotenv.php
Normal file
19
src/Symfony/Component/Runtime/Internal/MissingDotenv.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Runtime\Internal;
|
||||
|
||||
/**
|
||||
* @internal class that should be loaded only when symfony/dotenv is not installed
|
||||
*/
|
||||
class MissingDotenv
|
||||
{
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
// autoload_runtime.php @generated by Symfony Runtime
|
||||
|
||||
if (true === (require_once __DIR__.'/autoload.php') || empty($_SERVER['SCRIPT_FILENAME'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_object($app = require $_SERVER['SCRIPT_FILENAME'])) {
|
||||
throw new \TypeError(sprintf('Invalid return value: callable object expected, "%s" returned from "%s".', get_debug_type($app), $_SERVER['SCRIPT_FILENAME']));
|
||||
}
|
||||
|
||||
$runtime = $_SERVER['APP_RUNTIME'] ?? %runtime_class%;
|
||||
$runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? []) + %runtime_options%);
|
||||
|
||||
[$app, $args] = $runtime
|
||||
->getResolver($app)
|
||||
->resolve();
|
||||
|
||||
$app = $app(...$args);
|
||||
|
||||
exit(
|
||||
$runtime
|
||||
->getRunner($app)
|
||||
->run()
|
||||
);
|
19
src/Symfony/Component/Runtime/LICENSE
Normal file
19
src/Symfony/Component/Runtime/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2021 Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
112
src/Symfony/Component/Runtime/README.md
Normal file
112
src/Symfony/Component/Runtime/README.md
Normal file
@ -0,0 +1,112 @@
|
||||
Runtime Component
|
||||
=================
|
||||
|
||||
Symfony Runtime enables decoupling applications from global state.
|
||||
|
||||
**This Component is experimental**.
|
||||
[Experimental features](https://symfony.com/doc/current/contributing/code/experimental.html)
|
||||
are not covered by Symfony's
|
||||
[Backward Compatibility Promise](https://symfony.com/doc/current/contributing/code/bc.html).
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
```
|
||||
$ composer require symfony/runtime
|
||||
```
|
||||
|
||||
RuntimeInterface
|
||||
----------------
|
||||
|
||||
The core of this component is the `RuntimeInterface` which describes a high-order
|
||||
runtime logic.
|
||||
|
||||
It is designed to be totally generic and able to run any application outside of
|
||||
the global state in 6 steps:
|
||||
|
||||
1. the main entry point returns a callable that wraps the application;
|
||||
2. this callable is passed to `RuntimeInterface::getResolver()`, which returns a
|
||||
`ResolverInterface`; this resolver returns an array with the (potentially
|
||||
decorated) callable at index 0, and all its resolved arguments at index 1;
|
||||
3. the callable is invoked with its arguments; it returns an object that
|
||||
represents the application;
|
||||
4. that object is passed to `RuntimeInterface::getRunner()`, which returns a
|
||||
`RunnerInterface`: an instance that knows how to "run" the object;
|
||||
5. that instance is `run()` and returns the exit status code as `int`;
|
||||
6. the PHP engine is exited with this status code.
|
||||
|
||||
This process is extremely flexible as it allows implementations of
|
||||
`RuntimeInterface` to hook into any critical steps.
|
||||
|
||||
Autoloading
|
||||
-----------
|
||||
|
||||
This package registers itself as a Composer plugin to generate a
|
||||
`vendor/autoload_runtime.php` file. This file shall be required instead of the
|
||||
usual `vendor/autoload.php` in front-controllers that leverage this component
|
||||
and return a callable.
|
||||
|
||||
Before requiring the `vendor/autoload_runtime.php` file, set the
|
||||
`$_SERVER['APP_RUNTIME']` variable to a class that implements `RuntimeInterface`
|
||||
and that should be used to run the returned callable.
|
||||
|
||||
Alternatively, the class of the runtime can be defined in the `extra.runtime.class`
|
||||
entry of the `composer.json` file.
|
||||
|
||||
A `SymfonyRuntime` is used by default. It knows the conventions to run
|
||||
Symfony and native PHP applications.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
This `public/index.php` is a "Hello World" that handles a "name" query parameter:
|
||||
```php
|
||||
<?php
|
||||
|
||||
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||
|
||||
return function (array $request, array $context): void {
|
||||
// $request holds keys "query", "body", "files" and "session",
|
||||
// which map to $_GET, $_POST, $_FILES and &$_SESSION respectively
|
||||
|
||||
// $context maps to $_SERVER
|
||||
|
||||
$name = $request['query']['name'] ?? 'World';
|
||||
$time = $context['REQUEST_TIME'];
|
||||
|
||||
echo sprintf('Hello %s, the current Unix timestamp is %s.', $name, $time);
|
||||
};
|
||||
```
|
||||
|
||||
This `bin/console.php` is a single-command "Hello World" application
|
||||
(run `composer require symfony/console` before launching it):
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||
|
||||
return function (Command $command) {
|
||||
$command->addArgument('name', null, 'Who should I greet?', 'World');
|
||||
|
||||
return $command->setCode(function (InputInterface $input, OutputInterface $output) {
|
||||
$name = $input->getArgument('name');
|
||||
$output->writeln(sprintf('Hello <comment>%s</>', $name));
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
The `SymfonyRuntime` can resolve and handle many types related to the
|
||||
`symfony/http-foundation` and `symfony/console` components.
|
||||
Check its source code for more information.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
39
src/Symfony/Component/Runtime/Resolver/ClosureResolver.php
Normal file
39
src/Symfony/Component/Runtime/Resolver/ClosureResolver.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Runtime\Resolver;
|
||||
|
||||
use Symfony\Component\Runtime\ResolverInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @experimental in 5.3
|
||||
*/
|
||||
class ClosureResolver implements ResolverInterface
|
||||
{
|
||||
private $closure;
|
||||
private $arguments;
|
||||
|
||||
public function __construct(\Closure $closure, \Closure $arguments)
|
||||
{
|
||||
$this->closure = $closure;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resolve(): array
|
||||
{
|
||||
return [$this->closure, ($this->arguments)()];
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Runtime\Resolver;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @experimental in 5.3
|
||||
*/
|
||||
class DebugClosureResolver extends ClosureResolver
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resolve(): array
|
||||
{
|
||||
[$closure, $arguments] = parent::resolve();
|
||||
|
||||
return [
|
||||
static function (...$arguments) use ($closure) {
|
||||
if (\is_object($app = $closure(...$arguments)) || null === $app) {
|
||||
return $app;
|
||||
}
|
||||
|
||||
$r = new \ReflectionFunction($closure);
|
||||
|
||||
throw new \TypeError(sprintf('Unexpected value of type "%s" returned, "object" expected from "%s" on line "%d".', get_debug_type($app), $r->getFileName(), $r->getStartLine()));
|
||||
},
|
||||
$arguments,
|
||||
];
|
||||
}
|
||||
}
|
25
src/Symfony/Component/Runtime/ResolverInterface.php
Normal file
25
src/Symfony/Component/Runtime/ResolverInterface.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Runtime;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @experimental in 5.3
|
||||
*/
|
||||
interface ResolverInterface
|
||||
{
|
||||
/**
|
||||
* @return array{0: callable, 1: mixed[]}
|
||||
*/
|
||||
public function resolve(): array;
|
||||
}
|
48
src/Symfony/Component/Runtime/Runner/ClosureRunner.php
Normal file
48
src/Symfony/Component/Runtime/Runner/ClosureRunner.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Runtime\Runner;
|
||||
|
||||
use Symfony\Component\Runtime\RunnerInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @experimental in 5.3
|
||||
*/
|
||||
class ClosureRunner implements RunnerInterface
|
||||
{
|
||||
private $closure;
|
||||
|
||||
public function __construct(\Closure $closure)
|
||||
{
|
||||
$this->closure = $closure;
|
||||
}
|
||||
|
||||
public function run(): int
|
||||
{
|
||||
$exitStatus = ($this->closure)();
|
||||
|
||||
if (\is_string($exitStatus)) {
|
||||
echo $exitStatus;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (null !== $exitStatus && !\is_int($exitStatus)) {
|
||||
$r = new \ReflectionFunction($this->closure);
|
||||
|
||||
throw new \TypeError(sprintf('Unexpected value of type "%s" returned, "string|int|null" expected from "%s" on line "%d".', get_debug_type($exitStatus), $r->getFileName(), $r->getStartLine()));
|
||||
}
|
||||
|
||||
return $exitStatus ?? 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Runtime\Runner\Symfony;
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Runtime\RunnerInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @experimental in 5.3
|
||||
*/
|
||||
class ConsoleApplicationRunner implements RunnerInterface
|
||||
{
|
||||
private $application;
|
||||
private $defaultEnv;
|
||||
private $input;
|
||||
private $output;
|
||||
|
||||
public function __construct(Application $application, ?string $defaultEnv, InputInterface $input, OutputInterface $output = null)
|
||||
{
|
||||
$this->application = $application;
|
||||
$this->defaultEnv = $defaultEnv;
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
public function run(): int
|
||||
{
|
||||
if (null === $this->defaultEnv) {
|
||||
return $this->application->run($this->input, $this->output);
|
||||
}
|
||||
|
||||
$definition = $this->application->getDefinition();
|
||||
|
||||
if (!$definition->hasOption('env') && !$definition->hasOption('e') && !$definition->hasShortcut('e')) {
|
||||
$definition->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', $this->defaultEnv));
|
||||
}
|
||||
|
||||
if (!$definition->hasOption('no-debug')) {
|
||||
$definition->addOption(new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switches off debug mode.'));
|
||||
}
|
||||
|
||||
return $this->application->run($this->input, $this->output);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Runtime\Runner\Symfony;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpKernel\TerminableInterface;
|
||||
use Symfony\Component\Runtime\RunnerInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @experimental in 5.3
|
||||
*/
|
||||
class HttpKernelRunner implements RunnerInterface
|
||||
{
|
||||
private $kernel;
|
||||
private $request;
|
||||
|
||||
public function __construct(HttpKernelInterface $kernel, Request $request)
|
||||
{
|
||||
$this->kernel = $kernel;
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function run(): int
|
||||
{
|
||||
$response = $this->kernel->handle($this->request);
|
||||
$response->send();
|
||||
|
||||
if ($this->kernel instanceof TerminableInterface) {
|
||||
$this->kernel->terminate($this->request, $response);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Runtime\Runner\Symfony;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Runtime\RunnerInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @experimental in 5.3
|
||||
*/
|
||||
class ResponseRunner implements RunnerInterface
|
||||
{
|
||||
private $response;
|
||||
|
||||
public function __construct(Response $response)
|
||||
{
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function run(): int
|
||||
{
|
||||
$this->response->send();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
22
src/Symfony/Component/Runtime/RunnerInterface.php
Normal file
22
src/Symfony/Component/Runtime/RunnerInterface.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Runtime;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @experimental in 5.3
|
||||
*/
|
||||
interface RunnerInterface
|
||||
{
|
||||
public function run(): int;
|
||||
}
|
36
src/Symfony/Component/Runtime/RuntimeInterface.php
Normal file
36
src/Symfony/Component/Runtime/RuntimeInterface.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Runtime;
|
||||
|
||||
/**
|
||||
* Enables decoupling applications from global state.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @experimental in 5.3
|
||||
*/
|
||||
interface RuntimeInterface
|
||||
{
|
||||
/**
|
||||
* Returns a resolver that should compute the arguments of a callable.
|
||||
*
|
||||
* The callable itself should return an object that represents the application to pass to the getRunner() method.
|
||||
*/
|
||||
public function getResolver(callable $callable): ResolverInterface;
|
||||
|
||||
/**
|
||||
* Returns a callable that knows how to run the passed object and that returns its exit status as int.
|
||||
*
|
||||
* The passed object is typically created by calling ResolverInterface::resolve().
|
||||
*/
|
||||
public function getRunner(?object $application): RunnerInterface;
|
||||
}
|
205
src/Symfony/Component/Runtime/SymfonyRuntime.php
Normal file
205
src/Symfony/Component/Runtime/SymfonyRuntime.php
Normal file
@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Runtime;
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
use Symfony\Component\ErrorHandler\Debug;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\Runtime\Internal\MissingDotenv;
|
||||
use Symfony\Component\Runtime\Runner\Symfony\ConsoleApplicationRunner;
|
||||
use Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner;
|
||||
use Symfony\Component\Runtime\Runner\Symfony\ResponseRunner;
|
||||
|
||||
// Help opcache.preload discover always-needed symbols
|
||||
class_exists(ResponseRunner::class);
|
||||
class_exists(HttpKernelRunner::class);
|
||||
class_exists(MissingDotenv::class, false) || class_exists(Dotenv::class) || class_exists(MissingDotenv::class);
|
||||
|
||||
/**
|
||||
* Knows the basic conventions to run Symfony apps.
|
||||
*
|
||||
* Accepts the following options:
|
||||
* - "debug" to toggle debugging features
|
||||
* - "env" to define the name of the environment the app runs in
|
||||
* - "disable_dotenv" to disable looking for .env files
|
||||
* - "dotenv_path" to define the path of dot-env files - defaults to ".env"
|
||||
* - "prod_envs" to define the names of the production envs - defaults to ["prod"]
|
||||
* - "test_envs" to define the names of the test envs - defaults to ["test"]
|
||||
*
|
||||
* When these options are not defined, they will fallback:
|
||||
* - to reading the "APP_DEBUG" and "APP_ENV" environment variables;
|
||||
* - to parsing the "--env|-e" and "--no-debug" command line arguments
|
||||
* if the "symfony/console" component is installed.
|
||||
*
|
||||
* When the "symfony/dotenv" component is installed, .env files are loaded.
|
||||
* When "symfony/error-handler" is installed, it is registred in debug mode.
|
||||
*
|
||||
* On top of the base arguments provided by GenericRuntime,
|
||||
* this runtime can feed the app-callable with arguments of type:
|
||||
* - Request from "symfony/http-foundation" if the component is installed;
|
||||
* - Application, Command, InputInterface and/or OutputInterface
|
||||
* from "symfony/console" if the component is installed.
|
||||
*
|
||||
* This runtime can handle app-callables that return instances of either:
|
||||
* - HttpKernelInterface,
|
||||
* - Response,
|
||||
* - Application,
|
||||
* - Command,
|
||||
* - int|string|null as handled by GenericRuntime.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @experimental in 5.3
|
||||
*/
|
||||
class SymfonyRuntime extends GenericRuntime
|
||||
{
|
||||
private $input;
|
||||
private $output;
|
||||
private $console;
|
||||
private $command;
|
||||
private $env;
|
||||
|
||||
/**
|
||||
* @param array {
|
||||
* debug?: ?bool,
|
||||
* env?: ?string,
|
||||
* disable_dotenv?: ?bool,
|
||||
* project_dir?: ?string,
|
||||
* prod_envs?: ?string[],
|
||||
* dotenv_path?: ?string,
|
||||
* test_envs?: ?string[],
|
||||
* } $options
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->env = $options['env'] ?? null;
|
||||
$_SERVER['APP_ENV'] = $options['env'] ?? $_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null;
|
||||
|
||||
if (isset($_SERVER['argv']) && null === $this->env && class_exists(ArgvInput::class)) {
|
||||
$this->getInput();
|
||||
}
|
||||
|
||||
if (!($options['disable_dotenv'] ?? false) && isset($options['project_dir']) && !class_exists(MissingDotenv::class, false)) {
|
||||
(new Dotenv())
|
||||
->setProdEnvs((array) ($options['prod_envs'] ?? ['prod']))
|
||||
->bootEnv($options['project_dir'].'/'.($options['dotenv_path'] ?? '.env'), 'dev', (array) ($options['test_envs'] ?? ['test']));
|
||||
$options['debug'] ?? $options['debug'] = '1' === $_SERVER['APP_DEBUG'];
|
||||
}
|
||||
|
||||
parent::__construct($options);
|
||||
|
||||
if ($_SERVER['APP_DEBUG'] && class_exists(Debug::class)) {
|
||||
restore_error_handler();
|
||||
umask(0000);
|
||||
Debug::enable();
|
||||
}
|
||||
}
|
||||
|
||||
public function getRunner(?object $application): RunnerInterface
|
||||
{
|
||||
if ($application instanceof HttpKernelInterface) {
|
||||
return new HttpKernelRunner($application, Request::createFromGlobals());
|
||||
}
|
||||
|
||||
if ($application instanceof Response) {
|
||||
return new ResponseRunner($application);
|
||||
}
|
||||
|
||||
if ($application instanceof Command) {
|
||||
$console = $this->console ?? $this->console = new Application();
|
||||
$console->setName($application->getName() ?: $console->getName());
|
||||
|
||||
if (!$application->getName() || !$console->has($application->getName())) {
|
||||
$application->setName($_SERVER['argv'][0]);
|
||||
$console->add($application);
|
||||
}
|
||||
|
||||
$console->setDefaultCommand($application->getName(), true);
|
||||
|
||||
return $this->getRunner($console);
|
||||
}
|
||||
|
||||
if ($application instanceof Application) {
|
||||
if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
|
||||
echo 'Warning: The console should be invoked via the CLI version of PHP, not the '.\PHP_SAPI.' SAPI'.\PHP_EOL;
|
||||
}
|
||||
|
||||
set_time_limit(0);
|
||||
$defaultEnv = null === $this->env ? ($_SERVER['APP_ENV'] ?? 'dev') : null;
|
||||
$output = $this->output ?? $this->output = new ConsoleOutput();
|
||||
|
||||
return new ConsoleApplicationRunner($application, $defaultEnv, $this->getInput(), $output);
|
||||
}
|
||||
|
||||
if ($this->command) {
|
||||
$this->getInput()->bind($this->command->getDefinition());
|
||||
}
|
||||
|
||||
return parent::getRunner($application);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getArgument(\ReflectionParameter $parameter, ?string $type)
|
||||
{
|
||||
switch ($type) {
|
||||
case Request::class:
|
||||
return Request::createFromGlobals();
|
||||
|
||||
case InputInterface::class:
|
||||
return $this->getInput();
|
||||
|
||||
case OutputInterface::class:
|
||||
return $this->output ?? $this->output = new ConsoleOutput();
|
||||
|
||||
case Application::class:
|
||||
return $this->console ?? $this->console = new Application();
|
||||
|
||||
case Command::class:
|
||||
return $this->command ?? $this->command = new Command();
|
||||
}
|
||||
|
||||
return parent::getArgument($parameter, $type);
|
||||
}
|
||||
|
||||
private function getInput(): ArgvInput
|
||||
{
|
||||
if (null !== $this->input) {
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
$input = new ArgvInput();
|
||||
|
||||
if (null !== $this->env) {
|
||||
return $this->input = $input;
|
||||
}
|
||||
|
||||
if (null !== $env = $input->getParameterOption(['--env', '-e'], null, true)) {
|
||||
putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env);
|
||||
}
|
||||
|
||||
if ($input->hasParameterOption('--no-debug', true)) {
|
||||
putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0');
|
||||
}
|
||||
|
||||
return $this->input = $input;
|
||||
}
|
||||
}
|
1
src/Symfony/Component/Runtime/Tests/phpt/.env
Normal file
1
src/Symfony/Component/Runtime/Tests/phpt/.env
Normal file
@ -0,0 +1 @@
|
||||
SOME_VAR=foo_bar
|
21
src/Symfony/Component/Runtime/Tests/phpt/application.php
Normal file
21
src/Symfony/Component/Runtime/Tests/phpt/application.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
require __DIR__.'/autoload.php';
|
||||
|
||||
return function (array $context) {
|
||||
$command = new Command('go');
|
||||
$command->setCode(function (InputInterface $input, OutputInterface $output) use ($context) {
|
||||
$output->write('OK Application '.$context['SOME_VAR']);
|
||||
});
|
||||
|
||||
$app = new Application();
|
||||
$app->add($command);
|
||||
$app->setDefaultCommand('go', true);
|
||||
|
||||
return $app;
|
||||
};
|
12
src/Symfony/Component/Runtime/Tests/phpt/application.phpt
Normal file
12
src/Symfony/Component/Runtime/Tests/phpt/application.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Test Application
|
||||
--INI--
|
||||
display_errors=1
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
require $_SERVER['SCRIPT_FILENAME'] = __DIR__.'/application.php';
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
OK Application foo_bar
|
24
src/Symfony/Component/Runtime/Tests/phpt/autoload.php
Normal file
24
src/Symfony/Component/Runtime/Tests/phpt/autoload.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Runtime\SymfonyRuntime;
|
||||
|
||||
$_SERVER['APP_RUNTIME_OPTIONS'] = [
|
||||
'project_dir' => __DIR__,
|
||||
];
|
||||
|
||||
if (file_exists(dirname(__DIR__, 2).'/vendor/autoload.php')) {
|
||||
if (true === (require_once dirname(__DIR__, 2).'/vendor/autoload.php') || empty($_SERVER['SCRIPT_FILENAME'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$app = require $_SERVER['SCRIPT_FILENAME'];
|
||||
$runtime = new SymfonyRuntime($_SERVER['APP_RUNTIME_OPTIONS']);
|
||||
[$app, $args] = $runtime->getResolver($app)->resolve();
|
||||
exit($runtime->getRunner($app(...$args))->run());
|
||||
}
|
||||
|
||||
if (!file_exists(dirname(__DIR__, 6).'/vendor/autoload_runtime.php')) {
|
||||
throw new LogicException('Autoloader not found.');
|
||||
}
|
||||
|
||||
require dirname(__DIR__, 6).'/vendor/autoload_runtime.php';
|
15
src/Symfony/Component/Runtime/Tests/phpt/command.php
Normal file
15
src/Symfony/Component/Runtime/Tests/phpt/command.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
require __DIR__.'/autoload.php';
|
||||
|
||||
return function (Command $command, InputInterface $input, OutputInterface $output, array $context) {
|
||||
$command->setCode(function () use ($output, $context) {
|
||||
$output->write('OK Command '.$context['SOME_VAR']);
|
||||
});
|
||||
|
||||
return $command;
|
||||
};
|
12
src/Symfony/Component/Runtime/Tests/phpt/command.phpt
Normal file
12
src/Symfony/Component/Runtime/Tests/phpt/command.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Test Command
|
||||
--INI--
|
||||
display_errors=1
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
require $_SERVER['SCRIPT_FILENAME'] = __DIR__.'/command.php';
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
OK Command foo_bar
|
16
src/Symfony/Component/Runtime/Tests/phpt/command2.php
Normal file
16
src/Symfony/Component/Runtime/Tests/phpt/command2.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
require __DIR__.'/autoload.php';
|
||||
|
||||
return function (Command $command, InputInterface $input, OutputInterface $output, array $context) {
|
||||
$command->addArgument('name', null, 'Who should I greet?', 'World');
|
||||
|
||||
return static function () use ($input, $output, $context) {
|
||||
$output->writeln(sprintf('Hello %s', $input->getArgument('name')));
|
||||
$output->write('OK Command '.$context['SOME_VAR']);
|
||||
};
|
||||
};
|
13
src/Symfony/Component/Runtime/Tests/phpt/command2.phpt
Normal file
13
src/Symfony/Component/Runtime/Tests/phpt/command2.phpt
Normal file
@ -0,0 +1,13 @@
|
||||
--TEST--
|
||||
Test Command
|
||||
--INI--
|
||||
display_errors=1
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
require $_SERVER['SCRIPT_FILENAME'] = __DIR__.'/command2.php';
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Hello World
|
||||
OK Command foo_bar
|
20
src/Symfony/Component/Runtime/Tests/phpt/command_list.php
Normal file
20
src/Symfony/Component/Runtime/Tests/phpt/command_list.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Runtime\RuntimeInterface;
|
||||
|
||||
require __DIR__.'/autoload.php';
|
||||
|
||||
return function (Application $app, Command $command, RuntimeInterface $runtime) {
|
||||
$app->setVersion('1.2.3');
|
||||
$app->setName('Hello console');
|
||||
|
||||
$command->setDescription('Hello description ');
|
||||
$command->setName('my_command');
|
||||
|
||||
[$cmd, $args] = $runtime->getResolver(require __DIR__.'/command.php')->resolve();
|
||||
$app->add($cmd(...$args));
|
||||
|
||||
return $app;
|
||||
};
|
38
src/Symfony/Component/Runtime/Tests/phpt/command_list.phpt
Normal file
38
src/Symfony/Component/Runtime/Tests/phpt/command_list.phpt
Normal file
@ -0,0 +1,38 @@
|
||||
--TEST--
|
||||
Test "list" Command
|
||||
--INI--
|
||||
display_errors=1
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$argv = $_SERVER['argv'] = [
|
||||
'my_app',
|
||||
'list',
|
||||
'--env=prod',
|
||||
'--no-ansi',
|
||||
];
|
||||
$argc = $_SERVER['argc'] = count($argv);
|
||||
|
||||
require $_SERVER['SCRIPT_FILENAME'] = __DIR__.'/command_list.php';
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Hello console 1.2.3
|
||||
|
||||
Usage:
|
||||
command [options] [arguments]
|
||||
|
||||
Options:
|
||||
-h, --help Display %s
|
||||
-q, --quiet Do not output any message
|
||||
-V, --version Display this application version
|
||||
--ansi%A
|
||||
-n, --no-interaction Do not ask any interactive question
|
||||
-e, --env=ENV The Environment name. [default: "prod"]
|
||||
--no-debug Switches off debug mode.
|
||||
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
|
||||
|
||||
Available commands:
|
||||
help Display%S help for a command
|
||||
list List%S commands
|
||||
my_command Hello description
|
10
src/Symfony/Component/Runtime/Tests/phpt/hello.php
Normal file
10
src/Symfony/Component/Runtime/Tests/phpt/hello.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
require __DIR__.'/autoload.php';
|
||||
|
||||
return function (array $context): void {
|
||||
echo 'Hello World ', $context['SOME_VAR'];
|
||||
};
|
12
src/Symfony/Component/Runtime/Tests/phpt/hello.phpt
Normal file
12
src/Symfony/Component/Runtime/Tests/phpt/hello.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Test Request/Response
|
||||
--INI--
|
||||
display_errors=1
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
require $_SERVER['SCRIPT_FILENAME'] = __DIR__.'/hello.php';
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Hello World foo_bar
|
25
src/Symfony/Component/Runtime/Tests/phpt/kernel-loop.php
Normal file
25
src/Symfony/Component/Runtime/Tests/phpt/kernel-loop.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Runtime\SymfonyRuntime;
|
||||
use Symfony\Component\Runtime\Runner\ClosureRunner;
|
||||
use Symfony\Component\Runtime\RunnerInterface;
|
||||
|
||||
require __DIR__.'/autoload.php';
|
||||
|
||||
$runtime = new class(['project_dir' => __DIR__]) extends SymfonyRuntime {
|
||||
public function getRunner(?object $kernel): RunnerInterface
|
||||
{
|
||||
return new ClosureRunner(static function () use ($kernel): int {
|
||||
$kernel->handle(new Request())->send();
|
||||
echo "\n";
|
||||
$kernel->handle(new Request())->send();
|
||||
echo "\n";
|
||||
|
||||
return 0;
|
||||
});
|
||||