Архітектура

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

Архітектура

Ви - мій герой! Хто б міг подумати, що ви все ще будете тут, після перших трьох частин? Ваші зусилля скоро будуть винагороджені. Перші три частини ми не заглиблювались у розглядання архітектури фреймворку. Так як вона виділяє Symfony з натовпу фреймворків, давайте тепер пірнемо у світ архітектури.

Додавання логування

Новий застосунок Symfony мікроскопічний: він, по суті, складається просто з системи маршрутизації та контролера. Але завдяки Flex, установка нових функцій проста.

Хочете систему логування? Не проблема:

1
$ composer require logger

Це встановлює та конфігурує (через рецепт) потужну бібліотеку Monolog. Щоб використовувати логер в контролері, додайте новий аргумент, з підказкою LoggerInterface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
// src/Controller/DefaultController.php
namespace App\Controller;

use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class DefaultController extends AbstractController
{
    #[Route('/hello/{name}', methods: ['GET'])]
    public function index(string $name, LoggerInterface $logger): Response
    {
        $logger->info("Saying hello to $name!");

        // ...
    }
}

Ось і все! Нове повідомлення логу буде записане в var/log/dev.log. Шлях файлу логу або навіть інший метод логування можна сконфігурувати, оновивши один з файлів конфігурації, доданих рецептом.

Сервіси та автомонтування

Але зачекайте! Щойно сталося щось дуже круте. Symfony прочитала підказку LoggerInterface і автоматично зрозуміла, що має передати нам об'єкт Логера! Це називається автомонтування.

Кожна частина роботи, яка робиться в додатку Symfony, здійснюється об'єктом: об'єкт Логер записує логи, а об'єкт Twig відображає шаблони. Ці об'єкти називаються сервісами і вони є існтрументами, які допомагають вам будувати багаті функції.

Щоб зробити життя приголомшливим, ви можете попросити Symfony передати вам сервіс, використовуючи підказки. Які інші можливі класи або інтерфейси ви можете використати? Дізнайтеся, виконавши:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ php bin/console debug:autowiring

  # це тільки *маленький* приклад виведення...

  Описати екземпляр логера.
  Psr\Log\LoggerInterface (monolog.logger)

  Запросити стек, який контролює життєвий цикл запитів.
  Symfony\Component\HttpFoundation\RequestStack (request_stack)

  RouterInterface - це інтерфейс, який мають реалізовувати усі класи Маршрутизатора.
  Symfony\Component\Routing\RouterInterface (router.default)

  [...]

Це лише коротке резюме повного переліку! І по мірі додавання пакетів, цей список також зростатиме!

Створення сервісів

Щоб ваш код був впорядкованим, ви навіть можете створити ваші власні сервіси! Уявіть, що ви хочете згенерувати рандомне привітання (наприклад, "Привіт", "Йо", та ін.). Замість того, щоб розміщувати цей код напряму в контролері, створіть новий клас:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
// src/GreetingGenerator.php
namespace App;

class GreetingGenerator
{
    public function getRandomGreeting()
    {
        $greetings = ['Hey', 'Yo', 'Aloha'];
        $greeting = $greetings[array_rand($greetings)];

        return $greeting;
    }
}

Чудово! Ви можете використати це у вашому контролері негайно:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
// src/Controller/DefaultController.php
namespace App\Controller;

use App\GreetingGenerator;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class DefaultController extends AbstractController
{
    #[Route('/hello/{name}', methods: ['GET'])]
    public function index(string $name, LoggerInterface $logger, GreetingGenerator $generator): Response
    {
        $greeting = $generator->getRandomGreeting();

        $logger->info("Saying $greeting to $name!");

        // ...
    }
}

Ось і все! Symfony інстанціює GreetingGenerator автоматично і передасть його в якості аргументу. Але чи можемо ми також перемістити логіку логера в GreetingGenerator? Так! Ви можете використати автомонтування всередині сервісу, щоб отримати доступ до інших сервісів. Єдина різниця в тому, що це робиться у конструкторі:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
  // src/GreetingGenerator.php
+ use Psr\Log\LoggerInterface;

  class GreetingGenerator
  {
+     public function __construct(private readonly LoggerInterface $logger)
+     {
+     }

      public function getRandomGreeting(): string
      {
          // ...

+        $this->logger->info('Using the greeting: '.$greeting);

           return $greeting;
      }
  }

Так! Це теж працює: ніякої конфігурації, час не згаяно. Продовжуйте писати код!

Розширення та автоконфігурація Twig

Завдяки обробці сервісів Symfony, ви можете розширювати Symfony багатьма способами, на кшталт створення підписника подій або виборця безпеки для складних правил авторизації. Давайте додамо в Twig новий фільтр під назвою greet. Як? Просто створіть клас, що розширює AbstractExtension:

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
<?php
// src/Twig/GreetExtension.php
namespace App\Twig;

use App\GreetingGenerator;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;

class GreetExtension extends AbstractExtension
{
    public function __construct(private readonly GreetingGenerator $greetingGenerator)
    {
    }

    public function getFilters()
    {
        return [
            new TwigFilter('greet', [$this, 'greetUser']),
        ];
    }

    public function greetUser(string $name): string
    {
        $greeting =  $this->greetingGenerator->getRandomGreeting();

        return "$greeting $name!";
    }
}

Після створення всього одного файлу, ви можете одразу ж це використовувати:

1
2
3
{# templates/default/index.html.twig #}
{# Відобразить щось типу "Привіт, Symfony!" #}
<h1>{{ name|greet }}</h1>

Як це працює? Symfony помічає, що ваш клас розширює AbstractExtension і тому автоматично реєструє його в якості розширення Twig. Це називається автоконфігурацією та працює для дуже багатьох речей. Просто створіть клас, а потім розширьте базовий клас (або реалізуйте інтерфейс). Symfony попіклується про все інше.

Зі швидкістю вітру: Кешований контейнер

Побачивши, скілько всього Symfony робить автоматично, ви можете подумати: "Хіба це не шкодить продуктивності?". Насправді - ні! Symfony працює зі швидкістю вітру.

Як це можливо? Система сервісів керується дуже важлим об'єктом під назвою "контейнер". Більшість фреймворків мають контейнер, але в Symfony він унікальний, так як він "кешований". Коли ви завантажуєте вашу першу сторінку, вся інформація про сервіс була скомпільована та збережена. Це означає, що функції автомонтування та автоконфігурації не додають навантаження! Це також означає, що ви отримуєте чудові помилки: Symfony досліджує та валідує все, коли будується контейнер.

Тепер ви можете поцікавитися, що відбувається, коли ви оновлюєте файл і кешу необхідно побудуватися знову. Мені подобається ваш хід думок! Він достатньо розумний, щоб підлашутватися при наступному завантаженні сторінки. Але насправді це тема наступного розділу.

Розробка проти виробництва: середовища

Одним з головних завдань фреймворку є полегшення налагодження! І наш застосунок повний чудових інструментів для цього: панель інструментів веб-налагодження відображає знизу сторінки помилки - великі, чудові та повні - будь-яка конфігурація кешу автоматично будується знову за необхідності.

Але що якщо ви проводите розгортання у виробництві? Нам необхідно буде заховати ці інструменти та оптимізуватися для швидкості!

Це вирішується системою середовищ Symfony і їх існує три: dev, prod та test. В залежності від середовища, Symfony завантажує різні файли в каталог config/:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
config/
├─ services.yaml
├─ ...
└─ packages/
    ├─ framework.yaml
    ├─ ...
    ├─ **dev/**
        ├─ monolog.yaml
        └─ ...
    ├─ **prod/**
        └─ monolog.yaml
    └─ **test/**
        ├─ framework.yaml
        └─ ...
└─ routes/
    ├─ annotations.yaml
    └─ **dev/**
        ├─ twig.yaml
        └─ web_profiler.yaml

Це потужна ідея: змінюючу одну частину конфігурації (середовище), ваш застосунок трансформується з приємного досвіду налагодження в оптимізований для швидкості.

Ой, а як змінити середовище? Змініть змінну середовища APP_ENV з dev на prod:

1
2
3
# .env
- APP_ENV=dev
+ APP_ENV=prod

Але я хочу більше поговорити про змінні середовища далі. Змініть значення назад на dev: інструменти налагодження чудові, коли ви працюєте локально.

Змінні середовища

Кожний застосунок містить конфігурацію, яка відрізняється на кожному сервері - на кшталт інформації про з'єднання бази даних або паролей. Як їх треба зберігати? У файлах? Або якимось іншим чином?

Symfony слідує кращій практиці індустрії, зберігаючи конфігурацію, засновану на сервері, у вигляді змінних середовища. Це означає, що Symfony чудово працює з системами розгортання Платформи, як сервісу (PaaS), а також з Docker.

Але установка змінних середовища під час розробки може бути напруженою. Тому наш додато автоматично завантажує файл .env , якщо змінна середовища APP_ENV не встановлена у середовищі. Ключі в цьому файлі потім стають змінними середовища та зчитуються вашим додатком:

1
2
3
4
5
# .env
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=cc86c7ca937636d5ddf1b754beb22a10
###< symfony/framework-bundle ###

Спочатку файл не містить багато. Але із зростанням вашого додатку, ви додасте більше конфігурації за необхідності. Але, насправді, стає набагато цікавіше! Уявіть, що вашому додатку потрібна DB ORM. Давайте встановимо Doctrine ORM:

1
$ composer require doctrine

Завдяки новому рецепту, встановленому Flex, подивіться на файл .env ще раз:

1
2
3
4
5
6
7
8
9
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=cc86c7ca937636d5ddf1b754beb22a10
###< symfony/framework-bundle ###

+ ###> doctrine/doctrine-bundle ###
+ # ...
+ DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
+ ###< doctrine/doctrine-bundle ###

Нова змінна середовища DATABASE_URL була додана автоматично і на неї вже посилається новий файл конфігурації doctrine.yaml. Об'єднавши змінні середовища та Flex, ви використовуєте кращу практику індустрії без додаткових зусиль.

Продовжуйте!

Назвіть мене навіженим, але після прочитання цієї частини, вам має бути комфортно з найбільш важливими частинами Symfony. Все в Symfony створено, щоб не заважати вам писати код і додавати фукції, все зі швидкістю та якістю, яких ви хочете.

Ось і все для швидкого огляду. Від аутентифікації, до форм, до кешування - це стільки треба дослідити. Готові зануритися в ці теми? Тоді не шукайте, а переходьте на офіційний Документація Symfony, та обирайте будь-який довідник, який вам до вподоби.