besnovatyj / yii2-cms-config
Модуль управления конфигурацией для Yii2 CMS
Package info
github.com/besnovatyj/yii2-cms-config
Type:yii2-extension
pkg:composer/besnovatyj/yii2-cms-config
Requires
- php: >=8.4
- besnovatyj/yii2-cms-contracts: ^1.0
- besnovatyj/yii2-cms-kernel: ^1.0
- yiisoft/yii2: ~2.0.0
- yiisoft/yii2-bootstrap5: ~2.0.0
Requires (Dev)
- roave/security-advisories: dev-latest
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Модуль динамического управления конфигурацией приложения Yii2 с чистой архитектурой.
Возможности
- ✅ Динамическое изменение параметров приложения через UI
- ✅ Автоматический сбор настроек из всех модулей
- ✅ Гибкое хранилище (PHP файл или БД)
- ✅ Кэширование для производительности
- ✅ Валидация значений перед сохранением
- ✅ Dependency Injection через контейнер
Архитектура
┌─────────────────────────────────────────────────┐
│ Bootstrap │
│ (Применяет конфигурацию при запуске) │
└─────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ ConfigService │
│ (Оркестратор всей бизнес-логики) │
└──────┬──────────┬──────────┬────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌──────────┐ ┌──────────────┐
│ Collector │ │Repository│ │ Applier │
│(Сбор │ │(Хранение)│ │(Применение) │
│ опций) │ │ │ │ │
└───────────┘ └────┬─────┘ └──────────────┘
│
▼
┌────────────┐
│ Storage │
│ (PHP/БД) │
└────────────┘
Слои
Entity Layer
- ConfigItem - иммутабельный value object для элемента конфигурации
- ConfigValue - ActiveRecord для хранения в БД
Storage Layer
- StorageInterface - интерфейс хранилища
- PhpFileStorage - хранение в PHP файле (по умолчанию)
- DatabaseStorage - хранение в базе данных
Repository Layer
- ConfigRepository - абстракция над хранилищем
Service Layer
- ConfigCollector - собирает опции из
config/options.phpвсех модулей - ConfigApplier - применяет значения к приложению (без рекурсии!)
- ConfigService - главный сервис-оркестратор
Controller Layer
- ConfigController - тонкий контроллер с DI
Интерфейс администратора
Страница /Config/backend/config/index показывает параметры не плоским списком, а разделами:
- мини-меню разделов сверху — по одному пункту на модуль/категорию, со счётчиком параметров и точкой у разделов с изменёнными значениями;
- карточка на раздел — переключение разделов происходит на клиенте, все поля остаются в форме, поэтому одна кнопка «Сохранить» сохраняет правки любого числа разделов;
- поиск по названию, идентификатору, пути и описанию — по всем разделам сразу (не нужно помнить, какой модуль объявил параметр);
- фильтр «только изменённые» и подсветка параметров, значение которых переопределено (то есть отличается от дефолта из конфигурации модуля);
- сброс раздела к значениям по умолчанию — иконка в шапке карточки; сброс всех разделов — кнопка сверху.
Ссылка вида /Config/backend/config/index?category=Modman открывает страницу
сразу на нужном разделе (используется другими модулями).
Как параметр попадает в раздел
Ключ раздела определяет ConfigItem::groupKey():
categoryизoptions.php, если задана;- иначе ID модуля, объявившего опцию (заполняется
ConfigCollector); - иначе
app.
Раздел app («Приложение») всегда идёт первым, остальные — по алфавиту.
Необязательный ключ group разбивает параметры одного раздела на подгруппы с подзаголовками.
Использование
Добавление настроек в модуль
Создайте файл config/options.php в вашем модуле:
<?php return [ 'my_module_param' => [ 'path' => 'modules.mymodule.params.my_param', 'label' => 'My Parameter', 'description' => 'Description of parameter', 'category' => 'mymodule', // Раздел в UI; если не задан — берётся ID модуля 'group' => '', // Необязательная подгруппа внутри раздела 'rules' => [ ['required'], ['string'], ], 'inputOptions' => [ 'type' => 'input', // input, checkbox, textarea, dropdown ], ], ];
Доступ к параметрам в коде
// Получить значение параметра модуля $value = Yii::$app->getModule('mymodule')->params['my_param'];
Параметры для настройки
Параметры могут быть многоуровневыми массивами с любым уровнем вложенности.
// Пример как задаются return [ 'my_module_param' => [ 'path' => 'modules.mymodule.params.category.my_param', 'label' => 'My Parameter', 'description' => 'Description of parameter', 'category' => 'mymodule', // Для группировки в UI 'rules' => [ ['required'], ['string'], ], 'inputOptions' => [ 'type' => 'input', // input, checkbox, textarea, dropdown ], ], ]; // Получить значение параметра модуля $value = Yii::$app->getModule('mymodule')->params['category']['my_param'];
Программное сохранение значений
/** @var ConfigService $configService */ $configService = Yii::$container->get(ConfigService::class); $result = $configService->saveValues([ 'my_module_param' => 'new value', ]); if ($result['success']) { echo "Saved!"; }
Переданные значения дописываются к уже сохранённым: сохранение части параметров не стирает остальные. Неизвестные идентификаторы игнорируются.
Сброс к значениям по умолчанию
$configService->restoreDefaults(); // все разделы $configService->restoreDefaults('Modman'); // только один раздел
Переключение на БД хранилище
1. Измените config/container.php:
use Besnovatyj\Config\storage\DatabaseStorage; // вместо PhpFileStorage return [ 'singletons' => [ StorageInterface::class => [ 'class' => DatabaseStorage::class, // Изменить // ... ], ], ];
2. Измените Bootstrap.php:
В методе registerDependencies() замените PhpFileStorage на DatabaseStorage.
3. Примените миграции:
./yii migrate/up --migrationPath=@besnovatyj/Config/migrations
Типы полей ввода
Input
'inputOptions' => ['type' => 'input']
Checkbox
'inputOptions' => ['type' => 'checkbox']
Textarea
'inputOptions' => ['type' => 'textarea']
Dropdown
'inputOptions' => [ 'type' => 'dropdown', 'items' => [ 'value1' => 'Label 1', 'value2' => 'Label 2', ], ]
Dropdown со списком, который собирает система (itemsProvider)
Перечисленные вручную items годятся, пока список — это набор режимов («обрезать / вписывать»,
«да / нет»): он и правда постоянный.
Другое дело — список, который равен составу системы: установленные ядра поиска, движки
редактора, темы. Записанный литералом, он неизбежно расходится с правдой: поставили ещё один
пакет — надо не забыть дописать вариант; выключили модуль в менеджере модулей — вариант всё равно
предлагается администратору. Кроме того, такой список приходится держать в двух местах сразу —
в items и в правиле ['in', 'range' => ...].
Собирать список прямо в options.php нельзя: менеджер модулей считает от объявленных опций
контрольную сумму манифеста, и модуль с «плавающим» списком помечался бы как изменившийся при
каждой установке соседнего пакета. Поэтому объявление остаётся статичным, а вместо готового
массива указывается класс-поставщик:
'search_engine' => [ 'path' => 'modules.Search.params.engine', 'label' => '[Search] Активное ядро поиска', 'category' => 'Search', 'rules' => [['required']], 'inputOptions' => [ 'type' => 'dropdown', 'itemsProvider' => \Besnovatyj\Search\settings\EngineOptionItems::class, ], ],
Класс реализует контракт Besnovatyj\Contracts\config\OptionItemsProvider:
final class EngineOptionItems implements OptionItemsProvider { public function __construct(private readonly EngineRegistry $engines) { } /** @return array<string, string> значение => подпись */ public function items(): array { $items = []; foreach ($this->engines->descriptors() as $descriptor) { $items[$descriptor->key] = $descriptor->label; } return $items; } }
Что происходит дальше:
- поставщик резолвится DI-контейнером в момент отрисовки страницы настроек, то есть когда приложение уже поднято и знает, что установлено на самом деле; поэтому он может свободно зависеть от сервисов своего модуля через конструктор;
- список одновременно становится правилом валидации: сохранить значение вне набора ключей
нельзя, писать
['in', 'range' => ...]не нужно (и не следует — это был бы второй список, который разойдётся с первым); - сбой поставщика не роняет страницу: ошибка уходит в лог, поле остаётся пустым;
- на опции с обычным
itemsничего не меняется — механизм включается только при наличии ключаitemsProvider.
Правило выбора простое: перечисление режимов — items, состав системы — itemsProvider.
Валидация
Используйте стандартные валидаторы Yii2:
'rules' => [ ['required'], ['string', 'max' => 255], ['email'], ['integer', 'min' => 0], ['boolean'], ['in', 'range' => ['value1', 'value2']], ]
Расширение
Добавить новое хранилище (Redis)
use Besnovatyj\Config\storage\StorageInterface; class RedisStorage implements StorageInterface { public function get(string $id): ?string { /* ... */ } public function getAll(): array { /* ... */ } public function save(array $values): bool { /* ... */ } public function clear(): bool { /* ... */ } }
Зарегистрируйте в config/container.php или Bootstrap.php.
Добавить историю изменений
Расширьте DatabaseStorage:
class DatabaseStorageWithHistory extends DatabaseStorage { public function save(array $values): bool { // Сохранить в таблицу истории foreach ($values as $id => $value) { $history = new ConfigHistory(); $history->config_id = $id; $history->old_value = $this->get($id); $history->new_value = $value; $history->save(); } return parent::save($values); } }
Файлы
Bootstrap.php- регистрирует DI и применяет конфигурациюModule.php- главный класс модуляentities/- domain моделиstorage/- реализации хранилищrepositories/- работа с хранилищемservices/- бизнес-логикаcontrollers/backend/- контроллеры админкиconfig/container.php- DI конфигурацияconfig/schema.php- схема БД для DatabaseStorage
Требования
- PHP 8.0+
- Yii2 2.0.45+
- Yii2 DI Container