- PVSM.RU - https://www.pvsm.ru -

Альтернативная организация проекта на Yii2

Альтернативная организация проекта на Yii2 - 1 Как предлагается создавать проект на Yii2 сейчас? Выбираете шаблон проекта: basic или advanced, форкаете себе, потом пишете и комитите туда. Бам! Случилась копипаста, ваш проект и шаблон теперь развиваются отдельно. Вам не получить исправлений, внесенных, в шаблон, а в yii2-app-basic, естественно, не возьмут доработок специфических для вашей задачи. Это проблема номер один.

Как расширяется проект на Yii2? Выбираете подходящие расширения и подключаете их с помощью композера. Находите пример конфига этого расширения в README и копипастите в конфиг своего приложения. Оопс… Опять копипаста. Вылазящяя разными боками, в том числе таким: в большом проекте используется много расширений — конфиг приложения становится огромным и просто нечитаемым. Это проблема номер два.

Как эти проблемы связаны? Первая решается так: выделяем переиспользуемый код и превращаем в расширение. И снова здравствуйте: у расширения есть свой конфиг — получили вторую проблему.

Наиболее остро эти проблемы стоят для повторно используемых решений, когда надо поднимать много/несколько, в принципе одинаковых проектов, но с большими/маленькими изменениями. Плюс избавление от копипасты и переиспользование кода ещё никому не мешало.

Хочу поделиться своим вариантом решения этих проблем.

Система плагинов

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

Итак, решение такое: использовать систему плагинов — с самого начала создавать свой проект как плагин (расширение вместе с конфигом), делить проект на плагины и собирать конфиг приложения автоматически из конфигов плагинов.

Тут я должен приостановиться и объяснить что я называю плагином. В yii2 предусмотрены расширения (yii2 extension) и они дают возможность организовывать переиспользуемый код и подключать его к проекту композером. Но мало-мальски сложное расширение нуждается в конфигурации. И тут фреймворк не помогает. У создателя расширения есть два варианта:

  • описать желаемый конфиг в README и предложить програмеру его скопипастить;
  • сделать bootstrap [1] в своём расширении, который будет закидывать желаемый конфиг в конфиг приложения.

Первый вариант я уже покритиковал в самом начале, возьмусь за второй:

  • bootstrap запускается довольно рано, но всё таки объект Application уже создан и что-то уже просто не получится сконфигурировать;
  • довольно сложно правильно смержиться с конфигом уже созданного приложения, придётся работать не с целым массивом конфига, а по частям: компоненты отдельно (и очень нетривиально), алиасы отдельно, контейнер, модули, параметры, controllerMap,… (я пробовал — так счастья не видать);
  • bootstrap не ленивый, он запускается на каждый запрос к приложению и если таких bootstrap'ов много — они просто будут бить по производительности.

В общем, пройдя несколько итераций, намучавшись с разными вариантами родилось радикальное решение — собирать конфиг за пределами приложения и ещё до его запуска (хм, звучит просто и очевидно, но, как известно, хорошая мысля приходит опосля). Собирать оказалось удобнее всего плагином к композеру, из него есть удобный доступ
ко всей иерархии зависимостей проекта. Так получился composer-config-plugin [2].

Composer Config Plugin

Composer-config-plugin работает довольно просто:

  • обходит все зависимости проекта, находит в них описание конфигов плагинов в extra секции их composer.json;
  • мержит конфиги в соответствии с описанием и иерархией пакетов и записывает результирующие конфиг файлы.

В composer.json расширения (которое превращается в плагин) добавляются такие строчки:

    "extra": {
        "config-plugin": {
            "web": "src/config/web.php"
        }
    }

Это значит замержить в конфиг под названием web содержимое файла src/config/web.php. А в файле этом будет просто то, что плагин хочет добавить в конфиг приложения, например, конфиг интернационализации:

<?php

return [
    'components' => [
        'i18n' => [
            'translations' => [
                'my-category' => [
                    'class' => yiii18nPhpMessageSource::class,
                    'basePath' => '@myvendor/myplugin/messages',
                ],
            ],
        ],
    ],
];

Конфигов может быть сколько угодно, включая специальные: dotenv, defines и params. Конфиги обрабатываются в таком порядке:

  • переменные окружения — dotenv;
  • константы — defines;
  • параметры — params;
  • все остальные конфиги, например: common, console, web, ...

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

То есть: переменные окружения могут использоваться для назначения констант. Константы и переменные окружения могут использоваться для назначения параметров. И весь набор: параметры, константы и переменные окружения могут использоваться в конфигах.

В общем-то всё! composer-config-plugin просто мержит все массивы конфигов аналогом функции yiibasehelpersArrayHelper::merge. Естественно, конфиги мержатся в правильном порядке — с учётом кто кого реквайрит — таким образом, чтобы конфиг каждого пакета мержился после своих зависимостей и мог перезаписать значения заданные ими. Т.е. самый верхний пакет имеет полный контроль над конфигом и управляет всеми значениями, а плагины только задают дефолтные значения. В целом, процесс повторяет сборку конфигов в yii2-app-advanced, только более масштабно.

Изпользовать в приложении тривиально — добавляем в web/index.php:

$config = require hiqdevcomposerconfigBuilder::path('web');

(new yiiwebApplication($config))->run();

Найти больше информации и примеров, а также задать вопросы можно на гитхабе: hiqdev/composer-config-plugin [2].

Очень простой пример плагина hiqdev/yii2-yandex-plugin [3]. Но он наглядно демонстрирует возможности этого подхода. Чтобы получить счётчик Яндекс.Метрики достаточно зареквайрить плагин и задать параметр yandexMetrika.id. Всё! Не надо ничего копипастить в свой конфиг, не надо добавлять виджет в layout — не надо касаться рабочего кода. Плагин — это цельный кусок функционала, который позволяет расширять систему не внося изменений в существующий код.

Альтернативная организация проекта на Yii2 - 2— Что? Можно написать новую фичу, не поломав старые?!
— Да.
— Крутяк! Теперь можно не писать тесты?
— Нет… Так не бывает…

Итого, composer-config-plugin даёт систему плагинов и решает вопрос повторного использования так сказать "малых архитектурных форм". Пора вернуться к главному — организации больших переиспользуемых проектов. Повторю и уточню предлагаемое решение: создавать проект как систему плагинов, организованную в правильную иерархию.

Иерархия пакетов

Самый простой вариант организации проекта такой — наш проект реквайрит композером фреймворк и сторонние расширения ("сторонними" я называю не являющиеся частью нашего проекта), т.е. получается такая простая иерархия пакетов (репозиториев):

  • проект (выросший из шаблона приложения)
    • расширения
    • фреймворк

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

  • "корень"
    • плагины, специфичные для данного варианта проекта;
    • основной проект;
      • плагины проекта;
      • сторонние плагины;
      • базовый проект;
        • плагины, необходимые для работы базового проекта;
        • фреймворк.

Иерархия отображает кто кого реквайрит, т.е. корень реквайрит основной проект, тот в свою очередь — базовый проект, а базовый проект — фреймворк.

— Воу-воу! Полегче! Что за "корень" и "базовый проект"?

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

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

"Базовый проект" это то, во что превращается yii2-app-basic в этой схеме. Т.е. переиспользуемая основа приложения реализующая некоторый базовый функционал и оформленная в виде плагина. Эта запчасть не обязательна, но очень полезна. Вам не надо её делать самому, она может разрабатываться сообществом как сейчас разрабатывается yii2-app-basic. Мы разрабатываем HiSite, об этом ниже.

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

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

— Аааа! Нужен пример!

Например, Вы делаете на потоке сайты визитки. Базовый функционал везде одинаковый, но есть фичи за дополнительну плату, например каталог и, естественно, сайты отличаются внешним видом (темой) и кучей параметров. Это можно организовать в такую иерархию пакетов:

  • business-card-no42.com"корень";
    • myvendor/yii2-theme-cool — плагин, специфичный для данного сайта;
    • myvendor/business-card-catalog — плагин проекта, подключенный на данном сайте;
    • myvendor/business-card — основной проект;
      • myvendor/business-card-contacts — плагин проекта, используемый на всех сайтах;
      • othervendor/yii2-cool-stuffсторонний плагин;
      • hiqdev/hisite — базовый проект;
        • yiisoft/yii2-swiftmail — плагин, необходимый для работы базового проекта;
        • yiisoft/yii2 — фреймворк.

Надеюсь, не открыл Америки, и все более менее так и делят свои проекты на части. Только, наверно, без "корня". Попытаюсь донести его полезность.

"Корень"

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

  • .env — переменные окружения, например,ENV=prod;
  • composer.json — тут подключается основной проект и специфичные для него плагины;
  • src/config/params.php — явки, пароли, параметры проекта и используемых плагинов.

Пароли можно положить в .env и потом использовать их в params.php так:

return [
    'db.password' => $_ENV['DB_PASSWORD'],
];

Учитывая "легкоусвояемость" .env лучшими претендентами на вынос в .env являются параметры используемые другими (не PHP) технологиями.

Конечно, можно и нужно класть в "корень" некоторый конфиг и даже код, специфичный сугубо для данной инсталяции, не подлежащий копипастингу. Как только вижу копипасту, страшно её не люблю — уношу в какой-нибудь плагин.

Остальные файлы и каталоги необходимые для функционирования приложения (web/assets/, web/index.php) стандартны, их нужно создавать и назначать права "сборщиком" (build tool, task runner) мы велосипедим свой, но это уже совсем другая история.

По сути, "корень" — это params-local.php на стероидах. В нём концентрируется отличие конкретной инсталяции проекта от общего переиспользуемого кода. Мы создаём репозиторий под корень и храним его на нашем приватном git-сервере, поэтому комитим туда даже секреты (но это холиварная тема). Все остальные пакеты — в публичном доступе на GitHub. Мы комитим composer.lock в корне, поэтому перенос проекта на другой сервер делается просто composer create-project (я знаю — Docker получше будет, но об этом в следующий раз).

— А можно ещё конкретнее? Покажите мне код наконец!

HiSite и Asset Packagist

Одно из "базовых приложений", которые мы развиваем — HiSite hiqdev/hisite [4] — это основа для типичного сайта, как yii2-app-basic, только сделанная как плагин, что даёт все преимущества переиспользования кода над копипастингом:

  • можно основать свой проект на HiSite и получать его обновления;
  • можно со временем заменить базовый проект на другой, совместимый, но, например, с большим функционалом.

Шаблон "корня" для проекта на HiSite здесь — hiqdev/hisite-template [5].

Иерархия зависимостей выглядит так:

В README [5] корня описано как поднять проект у себя — composer create-project плюс настройка конфигурации. Благодаря реализации тем как плагинов и библиотеке тем [hiqdev/yii2-thememanager] в composer.json корня можно поменять yii2-theme-flat на yii2-theme-original запустить composer update и сайт переоденется в новую тему. Вот так просто.

Ещё один реальный рабочий проект, подходящий в качестве примера, сделанный, используя этот подход и полностью доступный на GitHub — Asset Packagist [9] — packagist-совместимый репозиторий, который позволяет устанавливать Bower и NPM пакеты как нативные composer пакеты.

Иерархия зависимостей выглядит так:

Подробности как поднять проект у себя описаны в README [10] корня.

Итоги подведём

Тема обширная, множество подробностей пришлось опустить. Надеюсь получилось донести общую идею. Ещё раз, используя введенную терминологию:

  • переиспользуем код в виде плагинов, т.е. код вместе с конфигурацией;
  • создаём проект как иерархию плагинов;
  • отделяем переиспользуемую часть проекта от конкретной инсталяции с помощью "корня".

Мы используем описанный подход около года, впечатления самые положительные — волосы стали мягкие и шелковистые: разделяем и властвуем, клепаем плагины легко и непринуждённо, 100+ [13] и останавливаться не собираемся, нужен новый функционал — делаем новый плагин.

Подход, в той или иной мере, применим для других фреймворков и даже языков… Ой, Остапа понесло… На сегодня хватит! Спасибо за внимание. Продолжение следует.

P.S.

На написание таких объёмов текста сподвигла серия [14] статей [15] Фабьена Потенсьера [16] (автора Symfony) про грядущий Symfony 4. Стыдно сказать, не до конца понял как именно всё работает, но уловил идеи и цели: система бандлов будет доработана в сторону их автоматической конфигурации, что в итоге даёт:

new way to create and evolve your applications with ease
новый способ создавать и развивать ваши приложения с лёгкостью

© Fabien Potencier

В общем, не один я считаю поднятые вопросы очень важными для фреймворка.

Я люблю Yii. Давайте сделаем в Yii лучше!

Автор: hiqsol

Источник [17]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/plugins/255973

Ссылки в тексте:

[1] bootstrap: http://www.yiiframework.com/doc-2.0/guide-structure-extensions.html#bootstrapping-classes

[2] composer-config-plugin: https://github.com/hiqdev/composer-config-plugin

[3] hiqdev/yii2-yandex-plugin: https://github.com/hiqdev/yii2-yandex-plugin

[4] hiqdev/hisite: https://github.com/hiqdev/hisite

[5] hiqdev/hisite-template: https://github.com/hiqdev/hisite-template

[6] hiqdev/yii2-theme-flat: https://github.com/hiqdev/yii2-theme-flat

[7] hiqdev/yii2-thememanager: https://github.com/hiqdev/yii2-thememanager

[8] yiisoft/yii2: https://github.com/yiisoft/yii2

[9] Asset Packagist: https://asset-packagist.org

[10] hiqdev/asset-packagist.dev: https://github.com/hiqdev/asset-packagist.dev

[11] hiqdev/yii2-theme-original: https://github.com/hiqdev/yii2-theme-original

[12] hiqdev/asset-packagist: https://github.com/hiqdev/asset-packagist

[13] 100+: https://hiqdev.com/packages

[14] серия: http://fabien.potencier.org/symfony4-compose-applications.html

[15] статей: http://fabien.potencier.org/symfony4-monolith-vs-micro.html

[16] Фабьена Потенсьера: http://fabien.potencier.org/

[17] Источник: https://habrahabr.ru/post/329286/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox