Как переиспользовать код с бандлами Symfony 5? Часть 2. Выносим код в бандл

от автора

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

В предыдущей статье мы создали минимальный бандл из двух файлов и подключили его в проект.

В этой статье:

  • Перенос кода в бандл
  • Dependency Injection: регистрация сервисов бандла в DI-контейнере
  • Перенос контроллеров и настройка роутинга
  • Механизм определения путей к ресурсам
  • Перенос шаблонов в бандл

Содержание серии

Часть 1. Минимальный бандл
Часть 2. Выносим код и шаблоны в бандл
Часть 3. Интеграция бандла с хостом: шаблоны, стили, JS
Часть 4. Интерфейс для расширения бандла
Часть 5. Параметры и конфигурация
Часть 6. Тестирование, микроприложение
Часть 7. Релизный цикл, установка и обновление

Если вы не последовательно выполняете туториал, то скачайте приложение из репозитория и переключитесь на ветку 1-bundle-mockup.

Инструкции по установке и запуску проекта в файле README.md.

Финальную версию кода для этой статьи вы найдете в ветке 2-basic-refactoring.

Приступим к рефакторингу.

Перемещаем основные файлы

Бандл может содержать все то же самое, что и обычные приложения Symfony: сущности, контроллеры и команды, шаблоны, ассеты, тесты и любой другой код.

Переместите файлы сущностей, репозиториев, формы и необходимые контроллеры в bundles/CalendarBundle/src (с сохранением структуры папок):

# Crate dirs cd bundles/CalendarBundle/src/ mkdir Controller Entity Service cd ../../../src  # Move files mv Form Repository ../bundles/CalendarBundle/src/ mv Controller/EditorController.php Controller/EventController.php ../bundles/CalendarBundle/src/Controller mv Entity/Event.php ../bundles/CalendarBundle/src/Entity mv Service/EventExporter ../bundles/CalendarBundle/src/Service/EventExporter mv Twig ../bundles/CalendarBundle/src/Twig

Чтобы перемещенный код заработал, нам потребуется обновить пространства имен перемещенных классов. Поменяйте везде после ключевых слов namespace и use корень App\ на корень бандла bravik\CalendarBundle\.

Внутри бандла не должно остаться никаких зависимостей от пространства имен App\: для бандла его не существует.

Все современные IDE имеют функцию поиска и замены по файлам.
Например в IDE PhpStorm выбираем папку бандла и жмем Ctrl/Cmd + Shift + R.

Кроме папки бандла нам потребуется сделать то же самое для use App\Repository\EventRepository в SiteController

Меняем везде и пробуем открыть главную страницу.

Получаем ошибку:

В SiteController мы внедряем зависимость: репозиторий EventRepository. С помощью механизма autowiring Symfony автоматически распознает typehints аргументов экшна и ищет нужный сервис среди зарегистрированных в DI-контейнере.

Но если в приложении все классы из папки src/Services автоматически регистрируются как сервисы, то в бандле никакие классы пока не зарегистрированы.

Регистрация сервисов бандла в Dependency Injection контейнере

Ключевая фишка бандла, это автоматическая подгрузка в DI-контейнер приложения своих зависимостей, прямо при установке.

Но если для пользователя бандла она автоматическая, то его разработчику нужно все настроить.

Как и в обычном приложении, для Symfony-бандла мы можем создать конфиг services.yaml, в котором будут описаны регистрируемые бандлом в контейнере DI-сервисы.

В корне бандла рядом src создайте папку и файл config/services.yaml:

parameters:     # Здесь могут быть параметры бандла  services:      # Конфигурация для всех сервисов  этого файла по умолчанию     _defaults:          # Включает механизм автоматической подстановки зависимостей контейнера         # в ваши сервисы по typehints аргументов конструктора (и экшнов контроллеров)         # https://symfony.com/doc/current/service_container.html#the-autowire-option         autowire: true          # Включает механизм автоконфигурации:         # сервисам автоматически добавляются теги по имплементируемым интерфейсам         # https://symfony.com/doc/current/service_container.html#the-autoconfigure-option         autoconfigure: true       # Регистрируем контроллеры бандла и репозиторий как DI-сервисы     bravik\CalendarBundle\Repository\EventRepository: ~     bravik\CalendarBundle\Controller\EventController: ~     bravik\CalendarBundle\Controller\EditorController: ~

Мы задали настройки по умолчанию для всего файла и зарегистрировали 3 сервиса.

Но в конфиге приложения-хоста config/services.yaml у нас осталось еще несколько строк, которые нужно перенести в бандл.

Закомментируйте помеченные @todo строки и перенесите следующие строки в бандл:

# Фильтр Twig для форматирования даты bravik\CalendarBundle\Twig\TwigRuDateFilter: ~  # Регистрируем все классы компонента EventExporter как DI-сервисы bravik\CalendarBundle\Service\EventExporter\:     resource: '../src/Service/EventExporter/*'  # Регистрируем ExporterProvider в качестве DI-сервиса # и явно инжектим 2 экспортера в конструктор bravik\CalendarBundle\Service\EventExporter\ExporterManager:     arguments:         $exporters:             - '@bravik\CalendarBundle\Service\EventExporter\Exporters\GoogleCalendarExporter'             - '@bravik\CalendarBundle\Service\EventExporter\Exporters\ICalendarExporter'

Если мы сейчас обновим страницу, то увидим, что ничего не изменилось. Файл конфигурации автоматически не подхватывается фреймворком, и нам нужно подключить его вручную. Для этого в папке src бандла создадим папку и класс DependencyInjection/CalendarExtension.php:

<?php namespace bravik\CalendarBundle\DependencyInjection;  use Exception; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension;  class CalendarExtension extends Extension {     public function load(array $configs, ContainerBuilder $container)     {     } }

Это очень важный для бандла класс. Он должен называться строго в формате <BundleName>Extension, располагаться в папке src/DependencyInjection и наследоваться от Symfony\Component\DependencyInjection\Extension\Extension.

Когда Symfony компилирует DI-контейнер, фреймворк проходится по всем подключенным бандлам и ищет в них именно Extension-файл. Если он существует, то вызывается метод Extension::load() в который передается собираемый контейнер приложения.

Это место, где мы можем добавить в контейнер собственные сервисы бандла. Технически мы могли обойтись и без файла конфигурации, и зарегистрировать все нужные сервисы прямо здесь в PHP-коде. Но удобней объявлять их привычным способом в отдельном конфигурационном файле.

Добавим servies.yaml бандла в сборку:

public function load(array $configs, ContainerBuilder $container) {     $loader = new YamlFileLoader(         $container,         new FileLocator(__DIR__.'/../../config')     );     $loader->load('services.yaml'); }

Снова обновим страницу, и снова получаем ошибку:

На этот раз приложение не может найти роут контроллера.

Настройки роутинга в бандле

В приложениях Symfony настройки роутинга прописываются в файлах:

config/routes.yaml              # Здесь указываются роуты config/routes/annotations.yaml  # Здесь подключаются роуты,                                 # размеченные аннотациями в контроллерах

В контроллерах нашего бандла роуты размечены аннотациями. Посмотрим как они подключаются в annotations.yaml:

controllers:                          # Произвольный идентификатор     type: annotation                  # Тип подкючаемого ресурса     resource: ../../src/Controller/   # Путь к файлам, размеченным аннотациями

Создадим такой же файл в папке с конфигами нашего бандла: config/routes.yaml.

calendar_routes:     type: annotation     resource: '../src/Controller/'

Теперь нужно подключить настройки бандла к конфигурации аннотаций приложения config/routes/annotations.yaml.

Добавим туда строки:

calendar_bundle:     resource: '@CalendarBundle/config/routes.yaml'

Здесь в качестве ресурса мы указываем наш конфиг в бандле.
@CalendarBundle — заменяет относительный путь к корню бандла.

Компонент роутинга Symfony имеет полезные для бандлов настройки префиксов.

Например, что если бы у нас была админка по адресу /admin и мы хотели бы наш редактор событий как-то в неё интегрировать: чтобы путь к нему начинался так же с /admin и чтобы он был спрятан за общей формой логина?

Мы можем добавить префиксы к роутам бандла, а при желании можем даже добавить префикс к именам роута:

calendar_bundle:     resource: '@CalendarBundle/config/routes.yaml'     prefix:   /admin     name_prefix: cms.

А дальше с помощью компонента security вы можете тонко настроить доступы и роли. Настройки ролей и доступов логично делать вне бандла, это специфичное для каждого приложения поведение.

Чтобы избежать конфликта имен, Symfony рекомендует всегда использовать в названиях роутов бандла префиксы vendor_name_. Мы не будем придерживаться этой рекомендации.

Пути к ресурсам бандла

Для обращения к ресурсам бандла в конфигах, а так же в шаблонах Twig у фреймворка предусмотрена «логическая» ссылка на бандл.

Её можно использовать в формате:

  • @<BundleName>Bundle/path/to/config — в файлах конфига
  • @<BundleName>/path/to/template — упрощенный вариант в шаблонах.

В Symfony это называется «логические пути». Их использование позволяет легко переопределять любые шаблоны бандла. Об этом позже.

Однако по умолчанию ссылка указывает не на корень бандла а на директорию ./src. Так сложилось исторически, потому что до Symfony 4 принято было ресурсы приложения и бандлов помещать внутри папки src/Resources. Начиная с 4 версии рекомендованная Symfony структура приложений и бандлов стала такой, какой вы видите её сейчас — чище и понятней. Однако легаси осталось и нам нужно внести небольшое изменение, чтобы это поправить.

Переопределим в главном файле бандла CalendarBundle метод getPath():

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

Теперь @CalendarBundle будет указывать на корень нашего бандла.

Обновим страницу, и на этот раз мы увидим наш календарь!

Но как же так? Мы ведь не скопировали в бандл шаблоны.

Посмотрим на наши контроллеры бандла. Обратите внимание, что они обращаются к шаблонам по абсолютным путям, а значит используют шаблоны из приложения-хоста.

Перенос шаблонов в бандл

Создадим в корне бандла папку templates для шаблонов и перенесем туда содержимое папки templates/event из приложения:

mkdir bundles/CalendarBundle/templates mv templates/event/* bundles/CalendarBundle/templates

Если мы обновим страницу — увидим, что шаблон не найден. Так и должно быть.

С помощью инструмента «Поиск и замена»вашей IDE замените в контроллерах бандла пути к шаблонам на логические относительно бандла. Для этого вхождения event/ в пути шаблона замените на логическую ссылку @Calendar/.

На самом деле @Calendar в шаблонах Twig это уже не совсем логическая ссылка, а namespace в терминологии Twig. Папку templates в путях указывать не нужно, так как Symfony автоматически зарегистрирует namespace, и ассоциирует его с папкой templates или Resources/views (если папки бандла организованы по старой конвенции).

Кроме этого на главной странице приложения в шаблоне site/index.html.twig мы используем виджет календаря из подключаемого twig-шаблона. Точно так же заменим путь к шаблону виджета на относительный путь из бандла:

Обновим страницу, — календарь снова на месте.

Но с шаблонами все еще остается еще одна проблема: некоторые шаблоны бандла унаследованы от базового шаблона base.html.twig приложения-хоста:

{% extends 'base.html.twig' %}

Этой проблемой мы займемся в следующей статье.

Резюме

Мы рассмотрели процесс переноса кода, шаблонов и ассетов в бандл, настроили роутинг и подключили сервисы бандла к сборке DI-контейнера. Финальный код Example Project для этой статьи в ветке 2-basic-refactoring.

  • Перенос кода в бандл, — это копирование файла с заменой namespace и путей импорта. В бандле не должно остаться зависимостей от пространства имен приложения App
  • Основной класс бандла, — Extension-класс. С его помощью можно вмешаться в компиляцию DI-контейнера приложения и подключить к нему собственные сервисы бандла. Удобно определять сервисы не в самом классе, а с помощью конфигурационного файла.
  • Роуты бандла можно определять в конфигурационных файлах внутри бандла. Эти файлы подключаются в настройках роутинга приложения хоста. Можно использовать аннотации в контроллерах бандла.
  • Пути к файлом ресурсов, в том числе к конфигах, шаблонам и т.д., определяются с помощью «логической» ссылки на бандл, указывающей на корневую папку бандла. С её помощью ресурсы бандла можно переопределять в приложении хосте.

В следующей статье разберемся как интегрировать шаблоны, JS и стили бандла в приложение-хост.

Другие статьи серии:

Часть 1. Минимальный бандл
Часть 2. Выносим код и шаблоны в бандл
Часть 3. Интеграция бандла с хостом: шаблоны, стили, JS
Часть 4. Интерфейс для расширения бандла
Часть 5. Параметры и конфигурация
Часть 6. Тестирование, микроприложение
Часть 7. Релизный цикл, установка и обновление

ссылка на оригинал статьи https://habr.com/ru/post/498536/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *