跳到内容

运行时组件

编辑此页

Runtime 组件将引导逻辑与任何全局状态解耦,以确保应用程序可以在运行时环境(如 PHP-PMReactPHPSwooleFrankenPHP 等)中运行,而无需进行任何更改。

安装

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 插件自动创建。此文件运行以下逻辑

  1. 它实例化了一个 RuntimeInterface
  2. 可调用对象(由 public/index.php 返回)被传递给 Runtime,Runtime 的工作是解析参数(在本例中为:array $context);
  3. 然后,调用此可调用对象以获取应用程序 (App\Kernel);
  4. 最后,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 负责将参数传递到闭包中并运行由闭包返回的应用程序。SymfonyRuntimeGenericRuntime 支持多种参数和不同的应用程序,您可以在前端控制器中使用它们。

可解析的参数

从前端控制器返回的闭包可以有零个或多个参数

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())。

以下参数同时受 SymfonyRuntimeGenericRuntime 支持(类型和变量名都很重要)

array $context
这与 $_SERVER + $_ENV 相同。
array $argv
传递给命令的参数(与 $_SERVER['argv'] 相同)。
array $request
具有键 querybodyfilessession

可解析的应用程序

以下闭包返回的应用程序是 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;
};

GenericRuntimeSymfonyRuntime 也支持以下通用应用程序

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"])
用于定义测试环境的名称。

除了这些之外,GenericRuntimeSymfonyRuntime 还支持以下选项

debug (默认值:由 debug_var_name 选项定义的环境变量的值
(通常为 APP_DEBUG),如果未定义此类环境变量,则为 true) 切换 Symfony 应用程序的 debug 模式(例如,显示错误)
runtimes
将“应用程序类型”映射到知道如何处理每种类型的 GenericRuntime 实现。
error_handler (默认值:BasicErrorHandlerSymfonyErrorHandler 用于 SymfonyRuntime)
定义用于处理 PHP 错误的类。
env_var_name (默认值:"APP_ENV")
定义存储 配置环境 名称的环境变量的名称,该名称在运行应用程序时使用。
debug_var_name (默认值:"APP_DEBUG")
定义存储 debug 模式 标志值的环境变量的名称,该标志值在运行应用程序时使用。

创建您自己的 Runtime

这是一个高级主题,描述了 Runtime 组件的内部结构。

使用 Runtime 组件将使维护人员受益,因为引导逻辑可以作为普通软件包的一部分进行版本控制。如果应用程序作者决定使用此组件,则 Runtime 类的软件包维护人员将拥有更多控制权,并且可以修复错误和添加功能。

Runtime 组件被设计为完全通用,并且能够在 6 个步骤中运行全局状态之外的任何应用程序

  1. 主入口点返回一个 *callable*(“app”),它包装了应用程序;
  2. *app callable* 被传递给 RuntimeInterface::getResolver(),它返回一个 ResolverInterface。此解析器返回一个数组,其中索引 0 处包含 app callable(或修饰此 callable 的内容),索引 1 处包含其所有已解析的参数。
  3. *app callable* 与其参数一起调用,它将返回一个表示应用程序的对象。
  4. 此 *应用程序对象* 被传递给 RuntimeInterface::getRunner(),它返回一个 RunnerInterface:一个知道如何“运行”应用程序对象的实例。
  5. 调用 RunnerInterface::run(object $application),它以 int 形式返回退出状态代码。
  6. 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();
};
包括代码示例在内,本作品根据 Creative Commons BY-SA 3.0 许可协议获得许可。
目录
    版本