Як створити додаток Symfony з декількома ядрами

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

Як створити додаток 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 завантажуватиме менше пакетів, ніж ядро за замовчуванням. Переконайтеся в тому, що ви також змінили локацію кешу, логів та файлів конфігурації, щоб вони не зіштовхувалися з файлами з 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
    {
        // завантажувати лише пакети, суворо необхідні для API
        return $this->getProjectDir().'/config/api_bundles.php';
    }
}

Крок 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
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