Компонент Runtime

Дата обновления перевода 2023-01-19

Компонент Runtime

Компонент Runtime (среда исполнения) отделяет логику самозагрузки от любого глобального состояния, чтобы убедиться в том, что приложение может работать со средами вроде PHP-FPM, ReactPHP, Swoole, и др. без изменений.

Установка

1
$ composer require symfony/runtime

Note

Если вы устанавливаете этот компонент вне приложения Symfony, вам нужно подключить файл vendor/autoload.php в вашем коде для включения механизма автозагрузки классов, предоставляемых Composer. Детальнее читайте в этой статье.

Использование

Компонент Runtime извлекает большинство логики самозагрузки как так называемые среды исполнения, позволяя вам писать фронт-контроллеры общим образом. Например, компонент Runtime позволяет public/index.php Symfony выглядеть так:

1
2
3
4
5
6
7
8
9
<?php
// public/index.php
use App\Kernel;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context) {
    return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

Итак, как же работает этот фронт-контроллер? Вначале, специальный файл autoload_runtime.php автоматически создается плагином Composer в компоненте. Этот файл выолняет следующую логику:

  1. Инстанциирует RuntimeInterface;
  2. Вызываемое (возвращенное public/index.php) передается Runtime, работа которого заключается в распознании аргументов (в этом примере: array $context);
  3. Затем, это вызываемое вызывается для получения приложения (App\Kernel);
  4. Наконец, Runtime используется для запуска приложения (т.e. вызов $kernel->handle(Request::createFromGlobals())->send()).

Caution

Если вы используете опцию 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) {
    $kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);

    // возвращение "Application" заставляет Runtime запустить Консольное
    // приложение вместо HTTP Ядра
    return new Application($kernel);
};

Выбор среды исполнения

Среда исполнения по умолчанию - SymfonyRuntime. Она отлично работает в большинстве приложений, работающих с веб-сервером, использующим PHP-FPM вроде Nginx или Apache.

Компонент также предоставляет GenericRuntime, которые использует суперглобальные $_SERVER, $_POST, $_GET, $_FILES и $_SESSION. Вы можете также использовать пользовательскую среду исполнения (например, чтобы интегрироваться с Swoole или AWS Lambda).

Используйте переменную окружения APP_RUNTIME или укажите extra.runtime.class в composer.json, чтобы изменить класс Runtime:

1
2
3
4
5
6
7
8
9
10
{
    "require": {
        "...": "..."
    },
    "extra": {
        "runtime": {
            "class": "Symfony\\Component\\Runtime\\GenericRuntime"
        }
    }
}

Использование Runtime

Runtime отвечает за передачу аргументов в замыкание и запуск приложения, которое возвращается этим замыканием. SymfonyRuntime и GenericRuntime поддерживает определенное количество аргументов и разных приложений, которые вы можете использовать в ваших фронт-контроллерах.

Разрешимые аргументы

Замыкание, возвращённое из фронт-контроллера, может иметь 0 или более аргументов:

1
2
3
4
5
6
7
8
9
10
<?php
// public/index.php
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) {
    // ...
};

Следующие аргументы поддерживаются 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. Однако, поддерживается ряд различных приложений:

1
2
3
4
5
6
7
8
9
<?php
// public/index.php
use App\Kernel;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function () {
    return new Kernel('prod', false);
};

SymfonyRuntime может работать с такими приложениями:

HttpKernelInterface
Приложение будет запущено с HttpKernelRunner, как и "стандартное" приложение Symfony.
Response

Ответ будет выведен ResponseRunner:

1
2
3
4
5
6
7
8
9
<?php
// public/index.php
use Symfony\Component\HttpFoundation\Response;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function () {
    return new Response('Hello world');
};
Command

Для написания приложений одной строки. Это будет использовать ConsoleApplicationRunner:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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->setCode(function (InputInterface $input, OutputInterface $output) {
        $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
20
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_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context) {
    $command = new Command('hello');
    $command->setCode(function (InputInterface $input, OutputInterface $output) {
        $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
16
<?php
// public/index.php
use Symfony\Component\Runtime\RunnerInterface;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function () {
    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
13
14
<?php
// public/index.php

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function () {
    $app = function() {
        echo 'Hello World';

        return 0;
    };

    return $app;
};
void

Если вызываемое ничего не возвращает, SymfonyRuntime предположит, что все хорошо:

1
2
3
4
5
6
7
<?php

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function () {
    echo 'Hello world';
};

Использование опций

Некоторое поведение сред исполнения можно изменять с помощью их опций. Они могут быть установлены, используя переменную окружения APP_RUNTIME_OPTIONS:

1
2
3
4
5
6
7
8
9
<?php

$_SERVER['APP_RUNTIME_OPTIONS'] = [
    'project_dir' => '/var/task',
];

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

// ...

Вы также можете сконфигурировать extra.runtime.options в composer.json:

1
2
3
4
5
6
7
8
9
10
{
    "require": {
        "...": "..."
    },
    "extra": {
        "runtime": {
            "project_dir": "/var/task"
        }
    }
}

Следующие опции поддерживаются SymfonyRuntime:

env (по умолчанию: переменная окружения APP_ENV или "dev")
Для определения названия окружения, в котором выполняется приложение.
disable_dotenv (по умолчанию: false)
Для отключения поиска файлов .env.
dotenv_path (по умолчанию: .env)
Для определения пути файлов dot-env.
dotenv_overload (default: false)
Чтобы сообщить Dotenv, надо ли переопределять переменные .env на .env.local (или другие файлы .env.*)
use_putenv
Для указания Dotenv установить переменные окружения, используя putenv() (НЕ РЕКОМЕНДУЕТСЯ).
prod_envs (по умолчанию: ["prod"])
Для определения названий окружений производства.
test_envs (по умолчанию: ["test"])
Для определения названий тестовых окружений.

Кроме этого, GenericRuntime и SymfonyRuntime также поддерживают такие опции:

debug (по умолчанию: значение переменной окружения, определенной опцией
debug_var_name (обычно - APP_DEBUG), или true, если такая переменная окружения не определена) Переключает режим отладки приложений Symfony (например, чтобы отображать ошибки)
runtimes
Маршрутизирует "типы приложений" к реализации GenericRuntime, которая знает, как работать с каждым из них.
error_handler (по умолчанию: BasicErrorHandler или SymfonyErrorHandler для SymfonyRuntime)
Определяет класс, который нужно использовать для обработки PHP-ошибок.
env_var_name (по умолчанию: "APP_ENV")
Определяет имя переменной окружения, которая хранит имя окружения конфигурации environment , чтобы использовать при запуске приложения.
debug_var_name (по умолчанию: "APP_DEBUG")
Определяет имя переменной окружения, которая хранит значение флажка режима отладки , чтобы использовать при запуске приложения.

Создайте свою собственную среду исполнения

Это продвинутая тема, которая описывает внутренний процесс компонента Runtime.

Использование компонента Runtime даст преимущества сопровождающим, так как логика самозагрузки может быть версионирована как часть обычного пакета. Если автор приложения решит использовать этот компонент, сопровождающий пакета класса Runtime будет иметь больше контроля и сможет исправлять ошибки и добавлять функции.

Компонент Runtime создан так, чтобы быть максимально общим и иметь возможность запускать любое приложение вне глобального состоания за 6 шагов:

  1. Основная точка входа возвращает вызываемое ("app"), оборачивающее приложение;
  2. Вызываемое приложения передается RuntimeInterface::getResolver(), который возвращает ResolverInterface. Этот разрешитель возвращает массив с вызываемым приложения (или что-то, что оформляет это вызываемое) с индексом 0 и все его разрешенные аргументы с индексом 1.
  3. Вызываемое приложения вызывается с его аргументами, и вернет объект, представляющий собой приложение.
  4. Этот объект приложения передаетя RuntimeInterface::getRunner(), который возвращает RunnerInterface: экземпляр, который знает, как "запускать" объект приложения.
  5. RunnerInterface::run(object $application) вызывается и возвращает статус-код выхода как `int`.
  6. PHP-движок завершает работу с этим статус-кодом.

При создании новой среды исполнения, важно подумать о двух вещах: Во-первых, какие аргументы будет использовать конечный пользователь? Во-вторых, как будет выглядеть приложние пользователя?

Например, представьте, что вы хотите создать среду исполнения для ReactPHP:

Какие аргументы будет использовать конечный пользователь?

Для общего приложения 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
39
40
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
{
    private $application;
    private $port;

    public function __construct(RequestHandlerInterface $application, int $port)
    {
        $this->application = $application;
        $this->port = $port;
    }

    public function run(): int
    {
        $application = $this->application;
        $loop = ReactFactory::create();

        // сконфигурируйте ReactPHP, чтобы корректно обработать приложение PSR-15
        $server = new ReactHttpServer(
            $loop,
            function (ServerRequestInterface $request) use ($application) {
                return $application->handle($request);
            }
        );

        // запустите сервер ReactPHP
        $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 $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);
        }

        // если это не приложение PSR-15, используйте GenericRuntime, чтобы
        // запустить приложение (см. "Resolvable Applications" выше)
        return parent::getRunner($application);
    }
}

Конечный пользователь теперь сможет создавать фронт-контроллер вроде:

1
2
3
4
5
6
7
<?php

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context) {
    return new SomeCustomPsr15Application();
};