Как создать приложение Symfony с несколькими ядрами

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

Как создать приложение Symfony с несколькими ядрами

Caution

Создание приложений с несколькими ядрами больше не рекомендуется Symfony. Рассмотрите вариант создания нескольких маленьких приложений вместо этого.

В большинстве приложений Symfony входящие запросы обрабатываются фронт контроллером public/index.php, который инстанциирует класс src/Kernel.php для создания ядра приложения, загружающего пакеты и работающего с запросом лля генерирования ответа.

Подход одного ядра - это удобное решение, но приложения Symfony могут определять любое количество ядер. В то время, как окружения выполняют одно и то же приложение с разными конфигурациями, ядра могут выполнять разные части одного и того же приложения.

Вот наиболее распространённые случаи применения создания множества ядер:

  • Приложение, которое определяет API, может определить два ядра по причине производительности. Первое ядро будет служить обычному приложению, а второе - реагировать только на API запросы, загружая пакеты и подключая меньше функций;
  • Высокочувствительное приложение может также определять два ядра. Первое будет только загружать маршруты, совпадающие с публичными частями приложения. Второе - загружать остальное приложение и его доступ будет защищён веб-сервером;
  • Приложение, ориентированное на микросервисы, может определять несколько ядер для выборочного подключения или отключения сервисов, превращая традиционное монолитное приложение в несколько микро-приложений.

Добавление нового ядра в приложение

Создание нового ядра в приложении - это процесс, состоящий из трёх шагов:

  1. Создайте новый фронт контроллер для загрузки нового ядра;
  2. Создайте новый класс ядра;
  3. Определите конфигурацию, загружаемую новым ядром.

Следующий пример показывает, как создать новое ядро для API заданного приложения Symfony.

Шаг 1) Создайте новый фронт контроллер

Вместо создания нового фронт контроллера с нуля, легче будет дублировать уже существующий. Например, создайте public/api.php из public/index.php.

Далее, обновите код нового фронт контроллера, чтобы инстанциировать новый класс ядра, вместо обычного класса Kernel:

1
2
3
4
5
6
7
// public/api.php
// ...
$kernel = new ApiKernel(
    $_SERVER['APP_ENV'] ?? 'dev',
    $_SERVER['APP_DEBUG'] ?? ('prod' !== ($_SERVER['APP_ENV'] ?? 'dev'))
);
// ...

Tip

Другой подход заключается в том, чтобы оставить существующий фронт контроллер index.php, но добавить утверждение if для загрузки другого ядра, основанного на URL (например, если URL начинается с/api, используйте ApiKernel).

Шаг 2) Создайте новый класс ядра

Теперь вам нужно определить класс ApiKernel, используемый новым фронт контроллером. Легче всего это сделать дублировав существующий файл src/Kernel.php` и внеся в него необходимые изменения.

В этом примере, ApiKernel будет загружать меньше пакетов, чем Kernel по умолчанию. Убедитесь в том, что вы также изменили локацию кеша, логов и файлов конфигурации, чтобы они не сталкивались с файлами из src/Kernel.php:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
// src/ApiKernel.php
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;

class ApiKernel extends Kernel
{
    use MicroKernelTrait;

    public function getProjectDir(): string
    {
        return \dirname(__DIR__);
    }

    public function getCacheDir(): string
    {
        return $this->getProjectDir().'/var/cache/api/'.$this->environment;
    }

    public function getLogDir(): string
    {
        return $this->getProjectDir().'/var/log/api';
    }

    protected function configureContainer(ContainerConfigurator $container): void
    {
        $container->import('../config/api/{packages}/*.yaml');
        $container->import('../config/api/{packages}/'.$this->environment.'/*.yaml');

        if (is_file(\dirname(__DIR__).'/config/api/services.yaml')) {
            $container->import('../config/api/services.yaml');
            $container->import('../config/api/{services}_'.$this->environment.'.yaml');
        } else {
            $container->import('../config/api/{services}.php');
        }
    }

    protected function configureRoutes(RoutingConfigurator $routes): void
    {
        $routes->import('../config/api/{routes}/'.$this->environment.'/*.yaml');
        $routes->import('../config/api/{routes}/*.yaml');
        // ... загружать только, если маршруты конфигурации нужны для API
    }

    // Если вам нужно выполнить некоторую логику, чтобы решить, какие пакеты загружать,
    // вы можете захотеть использовать вместо этого метод registerBundles()
    private function getBundlesPath(): string
    {
        // load only the bundles strictly needed for the API
        return $this->getProjectDir().'/config/api_bundles.php';
    }
}

5.4

Метод getBundlesPath() был представлен в Symfony 5.4.

Шаг 3) Определите конфигурацию ядра

Наконец, определите файлы конфигурации, которые будет загружать новый ApiKernel. Следуя коду выше, эта конфигурация будет жить в одном из множества файлов, хранящихся в каталогах config/api/ и config/api/ENVIRONMENT_NAME/.

Новые файлы конфигурации могут быть созданы с нуля при загрузке всего нескольких пакетов, так как это очень просто. В других случаях, дублируйте существующие вconfig/packages/ файлы конфигурации, или, что ещё лучше - импортируйте их и переопределите необходимые опции:

Выполнение команд с другим ядром

Скрипт bin/console, используемый для запуска команд Symfony всегда использует класс по умолчанию Kernel, чтобы построить приложение и загрузить команды. Если вам нужно выполнить консольные команды, используя новое ядро, дублируйте скрипт bin/console и переименуйте его (например, bin/api).

Далее, замените инстанциирование Kernel вашим собственным инстанциированием ядра (например, ApiKernel) и теперь вы можете выполнять команды, используя новое ядро (например, php bin/api cache:clear). Тепеоь вы можете выполнять команды, используя новое ядро.

Note

Команды, доступные для каждого конспольного скрипта (например, bin/console и bin/api) могут отличаться, так как они зависят от пакетов, подключенных дла каждого ядра, которые могут отличаться.

Отображение шаблонов, определённых в другом ядре

Если вы следуете Лучшим практикам Symfony, то шаблоны нового ядра будут храниться в templates/. Попытка отобразить эти шаблоны в другом ядре, приведут к ошибки Не существует зарегистрированных путей для пространста имён "__main__".

Чтобы решить эту проблему, добавьте следующую конфигурацию к вашему ядру:

1
2
3
4
5
# config/api/twig.yaml
twig:
    paths:
        # позволяет использовать api/templates/ dir в ApiKernel
        "%kernel.project_dir%/api/templates": ~

Выполнение тестов, используя другое ядро

В приложениях Symfony функциональные тесты по умолчанию расширяются из класса WebTestCase. Внутри этого класса, метод под названием getKernelClass() пытается найти класс ядра, для запуска приложения во время тестирования. Логика этого метода не поддерживает приложения с несколькими ядрами, так что ваши тесты не будут использовать правильное ядро.

Решением будет создать пользовательский базовый класс для функциональных тестов, расширяющийся из класса WebTestCase и переопределяющий метод getKernelClass(), чтобы возвращать полное имя класса ядра для использования:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

// тесты, которым нужно, чтобы ApiKernel работал, теперь должны расширять этот
// класс ApiTestCase вместо класса WebTestCase по умолчанию
class ApiTestCase extends WebTestCase
{
    protected static function getKernelClass()
    {
        return 'App\ApiKernel';
    }

    // это нужно, так как класс KernelTestCase содержит ссылку на созданное
    // ранее ядро и его статичное свойство $kernel. Следовательно, если ваши
    // функциональные тесты не запускают изолированный процесс, позднейший запуск
    // теста для другого ядра будет повторно использовать созданный ранее экземпляр,
    // указывающий на другое ядро
    protected function tearDown()
    {
        parent::tearDown();

        static::$class = null;
    }
}

Добавление большего количества ядер в приложение

Если ваше приложеиие очень сложное и вы создаёте несколько ядер, то лучше хранить их в собственных каталогах, вместо того, чтобы путаться с кучей файлов в каталоге src/ по умолчанию:

1
2
3
4
5
6
7
8
9
10
11
12
project/
├─ src/
│  ├─ ...
│  └─ Kernel.php
├─ api/
│  ├─ ...
│  └─ ApiKernel.php
├─ ...
└─ public/
    ├─ ...
    ├─ api.php
    └─ index.php