Архитектура

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

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

Новое приложение Symfony микроскопическое: оно по сути состоит просто из системы маршрутизации и контроллера. Но благодаря Flex, установка новых функций проста.

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

1
$ composer require logger

Это устанавливает и конфигурирует (через рецепт) мощную библиотеку Monolog. Чтобы использовать логгер в контроллере, добавьте новый аргумент, типизрованный LoggerInterface:

1
2
3
4
5
6
7
8
9
use Psr\Log\LoggerInterface;
// ...

public function index($name, LoggerInterface $logger)
{
    $logger->info("Saying hello to $name!");

    // ...
}

Вот и всё! Новое сообщение лога будет написано в var/log/dev.log. Конечно же, это можно сконфигурировать, обновив один из файлов конфигурации, добавленный рецептом.

Сервисы и автомонтирование

Но погодите! Только что случилось что-то очень крутое. Symfony прочла типизирование LoggerInterface и автоматически поняла, что должна передатьнам объект Логгера! Это называется автомонтирование.

Каждая часть работы, которая проделывается в приложении Symfony, осуществляется объектом: объект Логгер логирует, а объект Twig отображает шаблоны. Эти объекты называются сервисами и они являются инструментыми, которые помогаю вам строить богатые функции.

Чтобы сделать жизнь потрясающей, вы можете попросить Symfony передать вам сервис, используя типизирование. Какие другие возможные классы или интерфейсы вы можете использовать? Узнайте, выполнив:

1
$ php bin/console debug:autowiring
Тип класса/интерфейса ID сервиса союзника
Psr\Cache\CacheItemPoolInterface союзник для "cache.app.recorder"
Psr\Log\LoggerInterface союзник для "monolog.logger"
Symfony\Component\EventDispatcher\EventDispatcherInterface союзник для "debug.event_dispatcher"
Symfony\Component\HttpFoundation\RequestStack союзник для "request_stack"
Symfony\Component\HttpFoundation\Session\SessionInterface союзник для "session"
Symfony\Component\Routing\RouterInterface союзник для "router.default"

Это только краткое резюме полного списка! И по мере добавления пакетов, этот список также будет расти!

Создание сервисов

Чтобы ваш код был упорядоченым, вы даже можете создать ваши собственные сервисы! Представьте, что вы хотите сгенерировать рандомное приветствие (например, "Привет", "Йо", и др.). Вместо того, чтобы помещать этот код напрямую в контроллер, создайте новый класс:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 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
use App\GreetingGenerator;
// ...

public function index($name, LoggerInterface $logger, GreetingGenerator $generator)
{
    $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
20
+ use Psr\Log\LoggerInterface;

class GreetingGenerator
{
+     private $logger;
+
+     public function __construct(LoggerInterface $logger)
+     {
+         $this->logger = $logger;
+     }

    public function getRandomGreeting()
    {
        // ...

 +        $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
29
30
// src/Twig/GreetExtension.php
namespace App\Twig;

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

class GreetExtension extends AbstractExtension
{
    private $greetingGenerator;

    public function __construct(GreetingGenerator $greetingGenerator)
    {
        $this->greetingGenerator = $greetingGenerator;
    }

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

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

        return "$greeting $name!";
    }
}

После создания всего одного файла, вы можете сразу же это использовать:

1
2
{# Отобразит что-то вроде "Привет, 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 ###

Вначале файл не содержит многого. Но с ростом вашего приложения, вы добавите больше конфигурации по мере необходимости. Но, на самом деле, становится намного интереснее! Представьте, что вашему приложение нужно БД 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:[email protected]:3306/db_name
+ ###< doctrine/doctrine-bundle ###

Новая переменная окружения DATABASE_URL была добавлена автоматически и на неё уже ссылается новый файл конфигурации doctrine.yaml. Объединив переменные окружения и Flex, вы используете лучшую практику индустрии без дополнительных усилий.

Продолжайте!

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

Вот и всё для быстрого тура. От аутентификации, до форм, до кеширования - ещё столько предстоит исследовать. Готовы погрузиться в эти темы? Тогда не ищите, а переходите на официальный Symfony Documentation и выберите любой справочник, который хотите.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.