Как переиспользовать код с бандлами Symfony 5? Часть 7. Релизный цикл, установка и обновление

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


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


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


    • README.md
    • Установка: через composer, рецепты Flex, консольные команды
    • Релизный цикл, выпуск новых версий
    • Семантическое версионирование
    • Фиксация изменений в CHANGELOG.md


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


    Инструкции по установке и запуску проекта в файле README.md. Финальную версию кода для этой статьи вы найдете в ветке 7-support.


    Дисклеймер


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


    Установка бандла


    Чтобы установить готовый бандл в проект, потребуется всем знакомая команда composer:


    composer require bravik/calendar-bundle

    Эта команда выполнит необходимый минимум:


    1. Загрузит в папку vendors код бандла
    2. С помощью composer-плагина Symfony Flex, автоматически подключит бандл к вашему приложению в config/bundles.php.

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


    • В демо-бандле мы используем файл конфигурации с обязательными параметрами. Приложение не запустится до тех пор, пока мы не создадим в config/packages/ конфиг-файл и не пропишем туда обязательные параметры. Можно ли автоматизировать его создание при установке?
    • В этом же бандле мы используем сущности, размеченные аннотациями Doctrine. А значит, после подключения в проект нам потребуется сгенерировать миграции, а в некоторых случая может понадобиться занести при установке первоначальные данные в БД. Как это делать и как управлять изменениями схемы в дальнейшем?
    • Еще одна проблема — ассеты, сборка скриптов и стилей. Наверное, правильнее будет поставлять с бандлом уже собранную версию скриптов и стилей, и именно их подключать в проект. Но у меня в практике пока что всегда использовались исходники. И в этом случае возникает проблема: если зависимости composer бандла Symfony умеет автоматически находить, добавлять в общее дерево зависимостей и управлять ими, то с npm и зависимостями, определенными в package.json бандла этот фокус не пройдет. Приходится вручную добавлять зависимости бандла в проект.

    Что можно сделать, чтобы облегчить жизнь пользователя и максимально автоматизировать процесс?


    Пишите понятный и подробный README.md


    Не все действия можно автоматизировать. Очевидная, но обязательная рекомендация: описывайте подробно инструкции по установке в README.md файле.


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


    Документация незаменима при работе в команде, но пишите её даже если вы работаете над проектом один. Все это сэкономит ваше время и нервы, когда вы решите вернуться к разработке проекта после даже небольшого перерыва.


    Пример из Best Practices for Reusable Bundles


    Автоматизация с Symfony Flex


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


    Все это делает плагин composer Symfony Flex. Он позволяет определить так-называемый рецепт для установки бандла, в котором задаются необходимые для его инициализации действия. Для бандлов, у которых нет рецепта, используется авто-генерируемый рецепт. Он просто подключает бандл в проект и ничего более.


    Рецепты позволяют декларативно описать действия, которые должны выполняться при установке и удаления бандла. Инструмент дает удобный инструментарий, чтобы копировать произвольные файлы, конфиги, добавлять записи в .gitignore, ENV-параметры, запускать произвольные скрипты.

    Но можем ли мы тоже воспользоваться магией Symfony Flex? В то время как для официальных и серьезных open-source проектов Flex прекрасно работает, для приватных проектов все не так гладко.


    Дело в том, что рецепты по задумке должны храниться в отдельном от бандла репозитории. Их всего два: репозиторий для официальных бандлов Symfony и Contribution-репозиторий для всех остальных бандлов от сообщества Symfony. Contrib репозиторий публичный, принимает рецепты по заявкам и после модерации, что делает его бесполезными для частных проектов.


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


    Symfony Flex Private Repositories — Fabien Potencier, 2017
    Symfony 4 Using Private Recipes with Flex — Sebastian Sellmeier, 2018
    Symfony Flex Private Recipes: создание, настройка и использование — Юрий Богданов, 2017


    Однако, есть несколько недостатков:


    1. Фича в бете уже 1.5-2 года
    2. Flex Server бесплатный только в бете, а после беты 249 EUR в месяц.
    3. Flex Server требует регистрации на packagist, а приватные пакеты там только платные.
    4. Flex Server заставляет установить для своего бандла открытую лицензию MIT или BSD
    5. Есть баги, затрудняющие с ним работу. Этому багу 1.5 года.



    Все это делает Symfony Flex хорошим инструментом для официальной экосистемы Symfony и крупных open-source проектов Symfony-сообщества, но бесполезным для приватных бандлов на практике.


    Использование CLI-команд Symfony


    Итак, большую часть действий для запуска бандла нам придется выполнить вручную по инструкции из README.md


    Но некоторые действия все же можно автоматизировать: мы можем создать обычную консольную команду внутри бандла.


    mybundle/src/Command/InstallCommand.php:


    <?php
    
    namespace bravik\CalendarBundle\Command;
    
    use Symfony\Component\Console\Command\Command;
    use Symfony\Component\Console\Input\InputInterface;
    use Symfony\Component\Console\Output\OutputInterface;
    use Symfony\Component\Filesystem\Filesystem;
    
    class InstallCommand extends Command
    {
        protected static $defaultName = 'bravik:calendar:install';
    
        /** @var Filesystem */
        private $filesystem;
    
        private $projectDir;
    
        public function __construct(Filesystem $filesystem, string $projectDir)
        {
            parent::__construct();
    
            $this->filesystem = $filesystem;
            $this->projectDir = $projectDir;
        }
    
        protected function execute(InputInterface $input, OutputInterface $output): int
        {
            $output->writeln('Installing bravik/CalendarBundle...');
    
            $this->initConfig($output);
    
            // Прочие действия для инициализации
            // ...
    
            return 0;
        }
    
        private function initConfig(OutputInterface $output): void
        {
            // Create default config if not exists
            $bundleConfigFilename = $this->projectDir
                . DIRECTORY_SEPARATOR . 'config'
                . DIRECTORY_SEPARATOR . 'packages'
                . DIRECTORY_SEPARATOR . 'calendar.yaml'
            ;
            if ($this->filesystem->exists($bundleConfigFilename)) {
                $output->writeln('Config file already exists');
    
                return;
            }
    
            // Конечно лучше скопировать из готового файла
            $config = <<<YAML
    calendar:
      enable_soft_delete: true
    YAML;
            $this->filesystem->appendToFile($bundleConfigFilename, $config);
    
            $output->writeln('Config created: "config/packages/calendar.yaml"');
    
        }
    }

    Здесь мы проверяем, была ли выполнена инициализация ранее и определяем команду, которая создаст конфиг, если он еще не создан.


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


    Как вариант, можно написать не команду Symfony, а простой shell-script.


    Обновление бандла


    Как и любой программный продукт, бандл будет жить и развиваться. Поэтому о том, как будет организован процесс обновления нужно думать заранее.


    Если у вас простой бандл, то достаточно вести семантическое версионирование и учет изменений в CHANGELOG.md. Более сложные бандлы, столкнутся со специфическими проблемами: например как управлять изменениями в структурах данных и их представлением в БД


    Семантическое версионирование


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


    Формат версии:


    <major>.<minor>.<patch>

    где каждая из переменных — неотрицательное натуральное число.


    Выпуская новую версию увеличивают:


    • МАЖОРНУЮ версию, когда сделаны существенные изменения, ломающие обратную совместимость.
    • МИНОРНУЮ версию, когда вы добавляете новую функциональность, не нарушая обратной совместимости.
    • ПАТЧ-версию, когда вы вносите мелкие исправления или доработки, не нарушая обратной совместимости.

    Подробнее о семантическом версионировании


    Менеджер зависимостей Composer работает с семантическим версионированием и позволяет гибко организовать процесс учета зависимостей, отслеживания их изменений и автоматического обновления пакетов, в которых нет обратно-несовместимых изменений.


    Разберем на примере CalendarBundle.


    Как выпустить новую версию бандла?


    Версия бандла указывается в его composer.json:


    {
        "name": "bravik/calendar-bundle",
        "version": "0.1.0",
        ...
    }

    Для локальной разработки этого будет достаточно, но обычно Composer загружает пакет из удаленного git-репозитория. Чтобы понять, где искать нужную версию каждый релиз должен быть помечен тегом в формате соответствующей версии. Например:


    git tag v0.1.0

    После того как вы пушнете обновленный composer.json и новый тег в удаленный репозиторий — composer сможет обнаружить новую версию.


    Какую версию указывать для нового бандла?


    В composer.json приложения-хоста мы добавили:


    "require": {
        "bravik/calendar-bundle": "^0.1.0",
        ...
    },

    Знак ^ перед версией разрешает composer загружать все версии бандла, не ломающие обратную совместимость:


    • Для версий > 1.0.0 такими будут считаться все патч и минорные версии вплоть до 2.0.0.
    • До версии 1.0.0 все релизы считаются находящимися в фазе «активной разработки», поэтому безопасными будут считаться только патч-версии.

    Для нашего пакета безопасными будут считаться версии > 0.1.0 < 0.2.0.


    Как проверить доступность новых обновлений?


    Увеличим в bundles/CalendarBundle/composer.json патч версию:


    {
        "version": "0.1.1",
    }

    Выполним команду:


    composer outdated


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


    Теперь увеличим минорную версию бандла:


    {
        "version": "0.2.1",
    }


    Теперь Composer подсветил новую версию желтым. Это означает, что возможны изменения обратной совместимости. При обновлении composer не загрузит эту версию до тех пор, пока вы вручную не поднимите её в секции require composer.json приложения-хоста.


    Как обновлять?


    composer update

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


    Подробнее о том, как задавать ограничения версий Composer


    Учет изменений в CHANGELOG.md


    С развитием проекта, в вашем бандле неминуемо будут появляться изменения, ломающие обратную совместимость. Такие изменения необходимо отслеживать и документировать. Еще лучше отслеживать все основные изменения. Для этого используется файл CHANGELOG.md


    Зачем это нужно?


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


    Спустя несколько мажорных версий (или минорных если вы «в стадии активной разработки» и еще версионируете с 0.X.X) вам необходимо продолжить работу над вторым проектом. Чтобы обновиться придется пофиксить все изменения, ломающие обратную совместимость. Но без CHANGELOG.md — эта задача если и не невозможна, то крайне затруднительна. Проще будет удалить и начать сначала!


    Если же вы ведете CHANGElOG.md, то в нем вы можете указать основные изменения и необходимые для миграции действия.


    Я веду простой лог примерно в таком формате:


    # CHANGELOG
    Все значимые изменения документируются в этом файле
    
    [BC] - Пометка для breaking changes
    
    [BugFix] - Пометка для багов
    
    ## Unreleased
    Здесь копятся изменения до следующего релиза
     * CAL-5 [BC] Важное изменение
     * CAL-5 Добавлена крутая новая фича
    
    ## [0.2.0] (2020-04-06)
     * CAL-5 [BC] Экспортеры теперь подключаются по тегу
        - Неоходимо пометить экспортеры вне бандла тегами `calendar_bundle.exporter`
     * CAL-6 [BC] Добавлены новые поля в Мероприятие
        - Создайте новую миграцию, обратите внимание, что при миграции необходимо перенести данные из X в Y. Для этого добавьте в сгенерированную миграцию дополнительную SQL команду "XXX"
     * CAL-7 Код покрыт тестами
    
    ## [0.1.1] (2020-03-30)
    * CAL-4 [BugFix] Календарь не должен показывать удаленные мероприятия
    
    ## [0.1.0] (2020-02-15)
    * CAL-1 Добавлены основные функции
    * CAL-2 Добавлен шаблон календаря
    * CAL-3 Добавлен экспорт мероприятия
    * [BugFix] Editor should initialize blocks only when they change

    Посмотрите пример CHANGELOG.md symfony/framework-bundle. Symfony масштабный проект: для мажорных версий делается целая подборка критичных изменений. Например вот UPGRADE.md с Symfony 4 до Symfony 5.


    Проблема миграций


    Использование размеченных Doctrine сущностей создает проблемы с миграциями.


    Миграции специфичны для конкретного окружения. Они должны генерироваться и храниться в конкретном проекте, но не в бандле. Если изменения в базе достаточно просты — проблем может не возникнуть.


    Но если при миграции вам нужно сохранить и каким-то особым образом преобразовать уже имеющиеся данные возникнет проблема: где-то нужно хранить логику этих преобразований.


    Если в обычном приложении можно модифицировать миграцию и добавить туда нужные SQL запросы для преобразования, то что делать с бандлом, миграции для которого генерирует пользователь?


    Этот вопрос пока остается открытым.


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


    Резюме


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


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


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


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

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое