Компонент Runtime

Дата оновлення перекладу 2022-12-12

Компонент Runtime

Компонент Runtime відокремлює логіку самозавантаження від будь-якого глобального стану, щоб переконатися в тому, що додаток може працювати з середовищами на кшталт PHP-FPM, ReactPHP, Swoole, т ін. без змін.

Установка

1
$ composer require symfony/runtime

Note

Якщо ви встановлюєте цей компонент поза додатком Symfony, вам потрібно підключити файл vendor/autoload.phpу вашому коді для включення механізму автозавантаження класів, наданих Composer. Детальніше можна прочитати у цій статті.

Використання

Компонент 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) передається Рантайму, робота якого полягає в розпізнаванні аргументів (у цьому прикладі: array $context);
  3. Потім, це викличне викликається для отримання додатку (App\Kernel);
  4. Нарешті, Runtime використовується для запуску додатку (тобто, виклик $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" змушує Рантайм запустити Консольний
    // додаток замість 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, щоб змінити клас Рантайму:

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 - це спосіб використовувати користувацький додаток зі спільним Рантаймом:

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 (за замовчуванням: 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")
Визначає імʼя змінної середовища, яка зберігає імʼя середовища конфігурації , щоб викоритати при запуску додатку.
debug_var_name (за замовчуванням: "APP_DEBUG")
Визначає імʼя змінної середовища, яка зберігає значення прапорця режиму налагодження , щоб викоритати при запуску додатку.

Створіть своє власне середовище виконання

Це просунута тема, яка описує внутрішній процес компонента Рантайм.

Використання компонента 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();
};