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

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

В первой части:

  • Зачем нужны бандлы
  • Example Project: Calendar
  • Настраиваем окружение: 2 способа разработки
  • Создаем минимальный бандл
  • Подключение бандла в проект

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

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

Что такое бандл и зачем он нужен?

Symfony Bundle понадобится вам тогда (и только тогда), когда вы устанете копипастить код из проекта в проект и задумаетесь о его переиспользовании. Рано или поздно приходит понимание, что удобнее выделить код в переиспользуемый подключаемый модуль. Symfony Bundle — это и есть такой модуль в экосистеме Symfony.

Бандл — это пакет переиспользуемого PHP кода на стероидах Symfony Framework.

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

У такой тесной интеграции есть и обратная сторона, — пакет становится зависим от фреймворка. Хотя грамотная организация кода, использование DDD-подхода к разработке может помочь эту связность снизить.

Пример KnpMenuBundle

Взгляните на код одного из самых популярных бандлов Symfony: KnpMenuBundle.

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

Дело в том, что вы смотрите именно на «стероидную» часть бандла, отвечающую за интеграцию с Symfony приложениями. Вся бизнес-логика (домен) вынесена разработчиками в отдельный, независимый от фреймворка PHP-пакет, который сам подключается в бандл через Composer.

Example Project: Calendar

Разбираться с бандлами будем на примере рефакторинга приложения календаря.

Как переиспользовать код с бандлами Symfony 5? Часть 1. Минимальный бандл — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020
Дизайн заимствован у Яндекса и используется исключительно для образовательных целей.

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

Ваш календарь имеет успех, и еще 10 клиентов заказывают себе такой же.
Один из них заказывает новую фичу: уведомления о новых мероприятиях.
Остальные клиенты тоже хотят получить эту новую фичу, и теперь вам приходится копировать файлы между проектами, вручную обновлять файлы у всех 12 клиентов.

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

А теперь представьте, что вы выделили весь код вашего календаря в один, независимый от конкретного проекта, бандл. Теперь, при появлении ошибки, вам достаточно исправить код в одном месте, а в каждом из проектов для обновления достаточно выполнить composer update.

Попробуем отрефакторить приложение и вынести общую для календаря логику в бандл.

Склонируйте код из репозитория

В README.md короткая инструкция как развернуть и запустить приложение.

Приложение устроено просто:

  • сущность Event
  • 2 контроллера для отображения и редактирования мероприятий
  • сервис для экспорта календаря в различные форматы
  • набор шаблонов для виджета календаря и редактора
  • немного стилей, собираемых через webpack-encore

Начнем рефакторинг.

Настраиваем окружение

Первый вопрос, а как вообще разрабатывать бандл?

Ведь с одной стороны бандл — это отдельный проект, рука тянется к File -> New... -> Symfony Project. А с другой стороны он не может запускаться отдельно от приложения-хоста, в которое подключается.

Здесь есть 2 пути:

  1. Если бандл разрабатывается с нуля, можно создать микроприложение Symfony для разработки прямо внутри бандла. Мы вернемся к этому варианту в любом случае, так как микроприложение понадобится для тестирования.
    Плюсы Минусы
    чистота подхода сложно новичкам, ведь нужно хорошо понимать Symfony
    нужно потратить время на создание микроприложения
    наличие лишнего кода приложения хоста в проекте
  2. Если рефакторим существующий проект и хотим выделить его часть в бандл, то удобнее начать разработку прямо внутри этого проекта. Для этого выделяется отдельное место в проекте, например папка ./bundles.
    Плюсы Минусы
    быстро и просто привязка к приложению хосту, для доработки потребуется иметь доступ к двум репозиториям
    вы можете постепенно выносить логику в бандл и сразу же тестировать его на готовой инфраструктуре приложения-хоста нужно иметь в виду 2 репозитория в одном проекте (хотя PhpStorm отлично умеет разделять и справляться с этим)
    все в одном окне IDE, так проще требует внимательности: можно при разработки неочевидно воспользоваться зависимостями приложения хоста, что породит проблемы при подключении бандла в другой проект

К первому пути мы вернемся в статье о тестировании бандлов, а сейчас пойдем по второму пути.

Создаем минимальный бандл

Внутри ./bundles создадим основную папку будущего бандла CalendarBundle,
и внутри неё минимальный набор файлов:

src/CalendarBundle.php
composer.json

Всего 2 файла!

Разбираемся с composer.json

Скопируйте в composer.json бандла:

{
    "name": "bravik/calendar-bundle",
    "version": "0.1.0",
    "type": "symfony-bundle",
    "description": "Symfony bundles tutorial example project",
    "license": "proprietary",
    "require": {
        "php": "^7.3"
    },
    "require-dev": {
    },
    "config": {
        "sort-packages": true
    },
    "autoload": {
        "psr-4": {
            "bravik\CalendarBundle\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "bravik\CalendarBundle\Tests\": "tests/"
        }
    },
    "scripts": {
        "test" : "./vendor/bin/simple-phpunit"
    },
    "extra": {
        "symfony": {
            "allow-contrib": false,
            "require": "5.0.*"
        }
    }
}

Разберем содержимое:

"name": "bravik/calendar-bundle",
"description": "Health check bundle",

Эти обязательные поля устанавливают название пакета и описание. Для именования пакета по общепринятому соглашению используется название вендора и бандла. При установке с помощью менеджера зависимостей composer, код бандла будет помещен в соответствующую папку vendor/bravik/calendar-bundle

"type": "symfony-bundle"

Укажет Symfony, что этот пакет является бандлом. Благодаря специальному расширению для composer, — Symfony Flex, — при установке в приложение-хост, бандл автоматически будет подключен в ядро хоста (добавится в bundles.php), а так же запустится его «рецепт».

Рецепты — это механизм Symfony Flex, который позволяет при установке бандла выполнить специальный скрипт, задающий дополнительные действия по первичной настройке бандла.

"version": "0.1.0",

Задаст версию бандла. Менеджер зависимостей composer отслеживает и загружает обновления всех установленных в проект пакетов. С помощью семантического версионирования вы можете контролировать этот процесс. Подробней об этом позже

"autoload": {
    "psr-4": {
        "bravik\CalendarBundle\": "src"
    }
},
"autoload-dev": {
    "psr-4": {
        "bravik\CalendarBundle\Tests\": "tests"
    }
},

 

Здесь задается пространство имен бандла для автолоадера composer. По конвенции оно выбирается в формате <VendorName>/<CategoryName>/<BundleName>Bundle.

Благодаря этим строкам мы указываем механизму автозагрузки composer, что файлы с пространством имен bravik\CalendarBundle нужно искать в папке ./src относительно расположения composer.json бандла. При установке бандла в приложение-хост, эти настройки будут автоматически добавлены в общий автолоадер хоста, благодаря чему хост сможет использовать код бандла через use <BundleNamespace>/<BundleClass>.

Дополнительно укажем пространство имен для тестов для dev-окружения. Оно понадобиться нам позднее.

"require": {
    "php": "^7.3"
},
"require-dev": {},

В секции require и require-dev определяются зависимости для prod и dev окружения. Все эти зависимости будут добавлены в дерево зависимостей приложения-хоста и загружены автоматически при установке бандла. После установки их обновления будут отслеживаться через composer хоста. Кроме php нам пока ничего здесь не требуется.

Остальные опции нам не интересны.

Основной класс бандла

Создайте в ./src бандла файл CalendarBundle.php и скопируйте туда код:

<?php
namespace bravikCalendarBundle;

use SymfonyComponentHttpKernelBundleBundle;

class CalendarBundle extends Bundle
{
}

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

Имя файла имеет фиксированный формат: <BundleName>Bundle.php, — оно задает имя бандла и заканчивается словом Bundle. Если придерживаться этих конвенций, то Symfony при установке автоматически распознает и подключит ваш бандл, а так же создаст его псевдоним для внутреннего использования в ресурсах проекта (например в шаблонах).

Не нарушайте общепринятые соглашения без необходимости,
чтобы не усложнять себе жизнь.

Внутри файла мы используем корневое пространство имен bravikCalendarBundle, как мы установили в composer.json. Остальное содержимое главного класса бандла может быть пустым.

Подключение бандла в приложение-хост

Бандлы подключаются через composer с помощью привычной команды:

composer require bravik/calendar-bundle

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

Чтобы указать его местоположение, нужно добавить секцию repositories в composer.json хоста.

На время разработки вместо удаленного репозитория, мы подключим локальную папку bundles/CalendarBundle:

"repositories": [
    {
        "type" : "path",
        "url" : "./bundles/CalendarBundle"
    }
],

При таком подключении в папке vendor создасться не скачанная с репозитория копия нашего проекта, а симлинк bravik/calendar-bundle, указывающий на нашу локальную папку. Это позволит работать с бандлом как с внешней зависимостью из vendors, но иметь возможность редактировать файлы на локальной папки и сразу же видеть изменения.

Когда бандл достигнет релиза, мы вынесем его в отдельный git-репозиторий так:

"repositories": [
    {
        "type" : "vcs",
        "url" : "git@bitbucket.org:bravik/calendarbundle.git"
    }
],

В такой конфиграции composer склонирует git-репозиторий в папку vendors/bravik/calendar-bundle.

Подробнее о репозиториях composer.

Теперь после выполнения команды composer require bravik/calendar-bundle в composer.json хоста добавиться наш бандл в качестве зависимости, и подключиться к ядру. Чтобы убедиться в последнем, откроем config/bundles.php:

<?php

return [
    //...
    bravikCalendarBundleCalendarBundle::class => ['all' => true],
];

Мы видим, что наш бандл был подключен в проект с помощью его основного класса!

Инициализация репозиториев

На практике создавая бандл в локальной папке приложения-хоста, я сразу добавлю папку ./bundles в .gitignore приложения хоста, а внутри bundles/CalendarBundle инициализирую новый репозиторий: composer init. Зачем засорять репозиторий хоста лишним кодом, сразу можно выносить код бандла в отдельный репозиторий.

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

Но для удобства этой статьи, я храню код в монорепозитории.

Резюме

  • Минимальный Symfony бандл без полезной нагрузки состоит всего из 2х файлов: composer.json и класс MyBundle.
  • Начинать разработку бандла удобно прямо в проекте-доноре в одном окне IDE в одном GIT-репозитории, подключая локальную папку бандла через composer.
  • Когда бандл дойдет до стадии самостоятельного работоспособного пакета, выносите его в отдельный репозиторий.
  • Чтобы окончательно оторваться от хоста, можно создать микроприложение прямо внутри бандла.

Финальный код Example Project для этой статьи можно посмотреть в ветке 1-bundle-mockup.

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

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

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

Специально для сайта ITWORLD.UZ. Новость взята с сайта Хабр