运行时组件
Runtime 组件将引导逻辑与任何全局状态解耦,以确保应用程序可以在运行时环境(如 PHP-PM、ReactPHP、Swoole、FrankenPHP 等)中运行,而无需进行任何更改。
安装
1
$ composer require symfony/runtime
注意
如果您在 Symfony 应用程序之外安装此组件,则必须在代码中引入 vendor/autoload.php
文件,以启用 Composer 提供的类自动加载机制。请阅读 这篇文章 以了解更多详情。
用法
Runtime 组件将大多数引导逻辑抽象为所谓的 *runtimes*,允许您以通用方式编写前端控制器。例如,Runtime 组件允许 Symfony 的 public/index.php
看起来像这样
1 2 3 4 5 6 7 8
// public/index.php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context): Kernel {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};
那么这个前端控制器是如何工作的呢?首先,特殊的 autoload_runtime.php
文件由组件中的 Composer 插件自动创建。此文件运行以下逻辑
- 它实例化了一个 RuntimeInterface;
- 可调用对象(由
public/index.php
返回)被传递给 Runtime,Runtime 的工作是解析参数(在本例中为:array $context
); - 然后,调用此可调用对象以获取应用程序 (
App\Kernel
); - 最后,Runtime 用于运行应用程序(即调用
$kernel->handle(Request::createFromGlobals())->send()
)。
警告
如果您使用 Composer 的 --no-plugins
选项,则不会创建 autoload_runtime.php
文件。
如果您使用 Composer 的 --no-scripts
选项,请确保您的 Composer 版本为 >=2.1.3
;否则不会创建 autoload_runtime.php
文件。
为了制作控制台应用程序,引导代码应如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#!/usr/bin/env php
<?php
// bin/console
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context): Application {
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
// returning an "Application" makes the Runtime run a Console
// application instead of the HTTP Kernel
return new Application($kernel);
};
选择运行时
默认的 Runtime 是 SymfonyRuntime。它在大多数使用 PHP-FPM 的 Web 服务器(如 Nginx 或 Apache)上运行的应用程序中表现出色。
该组件还提供了一个 GenericRuntime,它使用 PHP 的 $_SERVER
、$_POST
、$_GET
、$_FILES
和 $_SESSION
超全局变量。您也可以使用自定义 Runtime(例如,与 Swoole 或 AWS Lambda 集成)。
使用 APP_RUNTIME
环境变量或在 composer.json
中指定 extra.runtime.class
来更改 Runtime 类
1 2 3 4 5 6 7 8 9 10
{
"require": {
"...": "..."
},
"extra": {
"runtime": {
"class": "Symfony\\Component\\Runtime\\GenericRuntime"
}
}
}
如果修改 runtime 类不足以满足您的需求,您可以创建自己的 runtime 模板
1 2 3 4 5 6 7 8 9 10
{
"require": {
"...": "..."
},
"extra": {
"runtime": {
"autoload_template": "resources/runtime/autoload_runtime.template"
}
}
}
Symfony 提供了一个 runtime 模板文件,您可以使用它来创建自己的模板。
使用 Runtime
Runtime 负责将参数传递到闭包中并运行由闭包返回的应用程序。SymfonyRuntime 和 GenericRuntime 支持多种参数和不同的应用程序,您可以在前端控制器中使用它们。
可解析的参数
从前端控制器返回的闭包可以有零个或多个参数
1 2 3 4 5 6 7 8 9 10
// public/index.php
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (InputInterface $input, OutputInterface $output): Application {
// ...
};
以下参数受 SymfonyRuntime
支持
- Request
- 从全局变量创建的请求。
- InputInterface
- 用于读取选项和参数的输入。
- OutputInterface
- 用于使用样式打印到 CLI 的控制台输出。
- Application
- 用于创建 CLI 应用程序的应用程序。
- Command
- 用于创建单行命令 CLI 应用程序(使用
Command::setCode()
)。
以下参数同时受 SymfonyRuntime
和 GenericRuntime
支持(类型和变量名都很重要)
array $context
- 这与
$_SERVER
+$_ENV
相同。 array $argv
- 传递给命令的参数(与
$_SERVER['argv']
相同)。 array $request
- 具有键
query
、body
、files
和session
。
可解析的应用程序
以下闭包返回的应用程序是 Symfony Kernel。但是,支持多种不同的应用程序
1 2 3 4 5 6 7 8
// public/index.php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return static function (): Kernel {
return new Kernel('prod', false);
};
SymfonyRuntime
可以处理以下应用程序
- HttpKernelInterface
- 应用程序将使用 HttpKernelRunner 运行,就像“标准” Symfony 应用程序一样。
- Response
-
Response 将由 ResponseRunner 打印
1 2 3 4 5 6 7 8
// public/index.php use Symfony\Component\HttpFoundation\Response; require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; return static function (): Response { return new Response('Hello world'); };
- Command
-
用于编写单命令应用程序。这将使用 ConsoleApplicationRunner
1 2 3 4 5 6 7 8 9 10 11 12 13
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 static function (Command $command): Command { $command->setCode(static function (InputInterface $input, OutputInterface $output): void { $output->write('Hello World'); }); return $command; };
- Application
-
对于具有多个命令的控制台应用程序很有用。这将使用 ConsoleApplicationRunner
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use Symfony\Component\Console\Application; 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 static function (array $context): Application { $command = new Command('hello'); $command->setCode(static function (InputInterface $input, OutputInterface $output): void { $output->write('Hello World'); }); $app = new Application(); $app->add($command); $app->setDefaultCommand('hello', true); return $app; };
GenericRuntime
和 SymfonyRuntime
也支持以下通用应用程序
- RunnerInterface
-
RunnerInterface
是一种将自定义应用程序与通用 Runtime 一起使用的方法1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// public/index.php use Symfony\Component\Runtime\RunnerInterface; require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; return static function (): RunnerInterface { return new class implements RunnerInterface { public function run(): int { echo 'Hello World'; return 0; } }; };
callable
-
您的“应用程序”也可以是
callable
。第一个可调用对象将返回“应用程序”,而第二个可调用对象是“应用程序”本身1 2 3 4 5 6 7 8 9 10 11 12
// public/index.php require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; return static function (): callable { $app = static function(): int { echo 'Hello World'; return 0; }; return $app; };
void
-
如果可调用对象不返回任何内容,则
SymfonyRuntime
将假定一切正常1 2 3 4 5
require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; return function (): void { echo 'Hello world'; };
使用选项
Runtimes 的某些行为可以通过运行时选项进行修改。它们可以使用 APP_RUNTIME_OPTIONS
环境变量进行设置
1 2 3 4 5 6 7
$_SERVER['APP_RUNTIME_OPTIONS'] = [
'project_dir' => '/var/task',
];
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
// ...
您还可以在 composer.json
中配置 extra.runtime
1 2 3 4 5 6 7 8 9 10
{
"require": {
"...": "..."
},
"extra": {
"runtime": {
"project_dir": "/var/task"
}
}
}
然后,更新您的 Composer 文件(例如,运行 composer dump-autoload
),以便使用新选项重新生成 vendor/autoload_runtime.php
文件。
以下选项受 SymfonyRuntime
支持
env
(默认值:APP_ENV
环境变量,或"dev"
)- 用于定义应用程序运行所在环境的名称。
disable_dotenv
(默认值:false
)- 用于禁用查找
.env
文件。 dotenv_path
(默认值:.env
)- 用于定义 dot-env 文件的路径。
dotenv_overload
(默认值:false
)- 告诉 Dotenv 是否使用
.env.local
(或其他.env.*
文件)覆盖.env
变量 use_putenv
- 告诉 Dotenv 使用
putenv()
设置环境变量(不推荐)。 prod_envs
(默认值:["prod"]
)- 用于定义生产环境的名称。
test_envs
(默认值:["test"]
)- 用于定义测试环境的名称。
除了这些之外,GenericRuntime
和 SymfonyRuntime
还支持以下选项
debug
(默认值:由debug_var_name
选项定义的环境变量的值- (通常为
APP_DEBUG
),如果未定义此类环境变量,则为true
) 切换 Symfony 应用程序的 debug 模式(例如,显示错误) runtimes
- 将“应用程序类型”映射到知道如何处理每种类型的
GenericRuntime
实现。 error_handler
(默认值:BasicErrorHandler 或 SymfonyErrorHandler 用于SymfonyRuntime
)- 定义用于处理 PHP 错误的类。
env_var_name
(默认值:"APP_ENV"
)- 定义存储 配置环境 名称的环境变量的名称,该名称在运行应用程序时使用。
debug_var_name
(默认值:"APP_DEBUG"
)- 定义存储 debug 模式 标志值的环境变量的名称,该标志值在运行应用程序时使用。
创建您自己的 Runtime
这是一个高级主题,描述了 Runtime 组件的内部结构。
使用 Runtime 组件将使维护人员受益,因为引导逻辑可以作为普通软件包的一部分进行版本控制。如果应用程序作者决定使用此组件,则 Runtime 类的软件包维护人员将拥有更多控制权,并且可以修复错误和添加功能。
Runtime 组件被设计为完全通用,并且能够在 6 个步骤中运行全局状态之外的任何应用程序
- 主入口点返回一个 *callable*(“app”),它包装了应用程序;
- *app callable* 被传递给
RuntimeInterface::getResolver()
,它返回一个 ResolverInterface。此解析器返回一个数组,其中索引 0 处包含 app callable(或修饰此 callable 的内容),索引 1 处包含其所有已解析的参数。 - *app callable* 与其参数一起调用,它将返回一个表示应用程序的对象。
- 此 *应用程序对象* 被传递给
RuntimeInterface::getRunner()
,它返回一个 RunnerInterface:一个知道如何“运行”应用程序对象的实例。 - 调用
RunnerInterface::run(object $application)
,它以int
形式返回退出状态代码。 - PHP 引擎以该状态代码终止。
创建新的 runtime 时,需要考虑两件事:首先,最终用户将使用哪些参数?其次,用户的应用程序会是什么样子?
例如,假设您要为 ReactPHP 创建一个 runtime
最终用户将使用哪些参数?
对于通用的 ReactPHP 应用程序,通常不需要特殊的参数。这意味着您可以使用 GenericRuntime。
用户的应用程序会是什么样子?
也没有典型的 React 应用程序,因此您可能需要依赖 PSR-15 接口进行 HTTP 请求处理。
但是,ReactPHP 应用程序将需要一些特殊的逻辑来 *运行*。该逻辑被添加到实现 RunnerInterface 的新类中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use React\EventLoop\Factory as ReactFactory;
use React\Http\Server as ReactHttpServer;
use React\Socket\Server as ReactSocketServer;
use Symfony\Component\Runtime\RunnerInterface;
class ReactPHPRunner implements RunnerInterface
{
public function __construct(
private RequestHandlerInterface $application,
private int $port,
) {
}
public function run(): int
{
$application = $this->application;
$loop = ReactFactory::create();
// configure ReactPHP to correctly handle the PSR-15 application
$server = new ReactHttpServer(
$loop,
function (ServerRequestInterface $request) use ($application): ResponseInterface {
return $application->handle($request);
}
);
// start the ReactPHP server
$socket = new ReactSocketServer($this->port, $loop);
$server->listen($socket);
$loop->run();
return 0;
}
}
通过扩展 GenericRuntime
,您可以确保应用程序始终使用此 ReactPHPRunner
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
use Symfony\Component\Runtime\GenericRuntime;
use Symfony\Component\Runtime\RunnerInterface;
class ReactPHPRuntime extends GenericRuntime
{
private int $port;
public function __construct(array $options)
{
$this->port = $options['port'] ?? 8080;
parent::__construct($options);
}
public function getRunner(?object $application): RunnerInterface
{
if ($application instanceof RequestHandlerInterface) {
return new ReactPHPRunner($application, $this->port);
}
// if it's not a PSR-15 application, use the GenericRuntime to
// run the application (see "Resolvable Applications" above)
return parent::getRunner($application);
}
}
最终用户现在可以创建如下所示的前端控制器
1 2 3 4 5
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context): SomeCustomPsr15Application {
return new SomeCustomPsr15Application();
};