Концепт конфигов Yii3

Обсуждаем разработку фреймворка: дизайн компонентов, API, пакеты
Ответить
roxblnfk
Сообщения: 3
Зарегистрирован: 2019.11.20, 21:16

Концепт конфигов Yii3

Сообщение roxblnfk » 2019.11.20, 22:22

Под словом конфиг подразумевается набор параметров в виде массива или объекта, объединённых по смыслу в пределах пакета или проекта. В одном пакете/проекте может быть несколько конфигов.

Пользователи обычно:
  • Читают имеющиеся конфиги.
  • Меняют существующий конфиг сервиса.
  • Добавляют конфиг для нового сервиса (обычно копипастой из ридми).
Конфигурация в Yii 2 была и хороша и плоха одновременно. Хороша тем, что ключи мапились напрямую на свойства конфигурируемых объектов. Плоха тем, что это тянуло за собой публичные свойства или сеттеры повсюду и не работало с PHP-кодом, который не был написан в стиле Yii 2.

Одна из целей Yii 3 — возможность конфигурировать и использовать любой PHP-код без обёрток. Широко используется DI-контейнер и композиция, поэтому конфиги получаются немного более сложными.

В общем в настоящий момент процесс выглядит примерно так (красная стрелка):

Изображение

Приложение запрашивает из контейнера экземпляр определённого интерфейса. Контейнер создаёт его или на основе конфига напрямую или при помощи фабрики. Конфиг фабрики обычно хранится в params.php, рядом с которым лежат соответственно common.php, web.php и console.php. Эти файлы мержатся и кешируются с помощью composer-config-plugin.

Концепт

Уже имеющийся подход довольно дружественный к пользователю, поэтому я предлагаю это оставить. Дополнения же следующие:
  1. Мы вводим объекты-конфиги (на диаграмме это зелёные стрелки и DB Config), описывая в них имена параметров и значения по умолчанию. Сама конфигурация при этом остаётся декларативной, как и ранее. В общем, конфижить придётся создавая объекты, но за нас это может и будет делать контейнер.
  2. Если мы используем params.php, то в качестве ключа конфига используем имя класса, описывающего конфиг (пример).
Это сразу даёт:
  • Конфиг в виде объекта можно запросить из контейнера.
  • Независимость от $params (или даже плагина composer-config-plugin) — можно конфижить вручную или через фабрики контейнера, создавая конфиг-объекты.
  • Из предыдущего пункта следует, что интеграция модуля в сторонние фреймворки может быть проще.
  • При клике по имени класса можно быстро перейти к его описанию и посмотреть, какие поля можно указать в конфиге, что они означают и какие значения принимают. Это сильно улучшает общую читаемость конфига.
  • Валидацию или преобразование переданных значений можно производить прямо конфиг-классом. Этим также решиться проблема с описками в конфиге при его изменении. Сейчас описки проходят незамеченными, настройка откатывается к умолчанию.
Сейчас концепт частично реализован в виде PR https://github.com/yiisoft/yii-cycle/pull/5

Разбор реализации

Начнём с конфигурации в params.php:

Код: Выделить всё

# было
'cycle.migration' => [
    'directory' => '@root/migrations',
    'namespace' => 'App\\Migration',
    'table' => 'migration',
    'safe' => false,
],
# стало
CycleMigrationConfig::class => [
    'directory' => '@root/migrations',
    'namespace' => 'App\\Migration',
    'table' => 'migration',
    'safe' => false,
],
Вместо ключа 'cycle.migration' используется имя класса CycleMigrationConfig (класс, описывающий конфигурацию миграций).
Поскольку мы изменили params.php, нужно пересобрать конфигурационные файлы (обновить в кеше), как того требует composer-config-plugin.

Код: Выделить всё

> composer du
После того, как params.php смержится и закешируется, composer-config-plugin примется за обработку файлов с настройками контейнера (common.php, web.php, console.php...). И когда дело дойдет до common.php, специальным генератором будет проанализирован params.php, и все параметры оттуда, относящиеся к конфиг-файлам, будут автоматически продублированы в кеше common.php в виде правил для создания соответствующих конфиг-объектов:

Код: Выделить всё

'Yiisoft\\Yii\\Cycle\\CycleMigrationConfig' => [
    '__class' => 'Yiisoft\\Yii\\Cycle\\CycleMigrationConfig',
    'configure()' => [
        [
            'directory' => '@root/migrations',
            'namespace' => 'App\\Migration',
            'table' => 'migration',
            'safe' => false,
        ],
    ],
],
Таким образом, контейнер будет сразу создавать объект конфига с нужными данными и без обращения к params.php в рантайме.

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

Для каждого поля (свойства) конфига могут быть определены методы-сеттеры и методы-геттеры. Сеттеры будут использованы во время заполнения конфига-объекта значениями (если сеттер для поля не определен, то значение будет записано в свойство напрямую). Геттеры автоматически вызываются при получении непубличных свойств, в том числе при получении конфига в виде массива методом toArray(). Если геттер для какого-либо поля не описан, его всё-равно можно вызвать для получения значения. Этим объясняется наличие магии и аннотаций @method для геттеров в базовом классе конфигов.

Пример сеттера в классе CycleDbalConfig:
Если указать в настройках соединения путь до файла с использованием альяса, то альяс в этом сеттере будет преобразован в реальный путь.

Код: Выделить всё

'connection' => 'sqlite:@runtime/database.db'
Аналогичный пример геттера в классе CycleMigrationConfig, но в этом случае преобразование альяса в реальный путь будет происходить при запросе значения directory.

Код: Выделить всё

protected $directory = '@root/migrations';

/** @var Yiisoft\Aliases\Aliases */
private $objAliases;

protected function getDirectory(): string
{
    return $this->convertAlias($this->directory);
}
protected function convertAlias(string $alias): string
{
    return $this->objAliases->get($alias, true);
}
Таким образом, используя сеттеры и геттеры, мы можем производить необходимые преобразования данных.

О валидации

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

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

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

Аватара пользователя
mat.twg
Сообщения: 218
Зарегистрирован: 2012.02.22, 20:44
Откуда: Санкт-Петербург

Re: Концепт конфигов Yii3

Сообщение mat.twg » 2019.11.25, 22:48

Конфиг в виде объекта можно запросить из контейнера.
Зачем? Конфиги лежат себе и лежат, трогать их без надобности не надо
Независимость от $params (или даже плагина composer-config-plugin) — можно конфижить вручную или через фабрики контейнера, создавая конфиг-объекты.
Тоже не ясно, какая зависимость от params? Ну есть params и есть... политика конфигов для приложения read only, не надо над ними никаких манипуляций кроме чтения...
Из предыдущего пункта следует, что интеграция модуля в сторонние фреймворки может быть проще.
Относительно...
При клике по имени класса можно быстро перейти к его описанию и посмотреть, какие поля можно указать в конфиге, что они означают и какие значения принимают. Это сильно улучшает общую читаемость конфига.
Ну да, не поспоришь...
Валидацию или преобразование переданных значений можно производить прямо конфиг-классом. Этим также решиться проблема с описками в конфиге при его изменении. Сейчас описки проходят незамеченными, настройка откатывается к умолчанию.
php итак выкинет тебя с эксепшином при неверном ключе/значении в момент инициализации, зачем это масло масленное с вадидацией?

Что-то так пока и не увидел профита, звучит как конфиги на моделях... сразу оговорюсь, я исключительно про концепт, абстрактно.

roxblnfk
Сообщения: 3
Зарегистрирован: 2019.11.20, 21:16

Re: Концепт конфигов Yii3

Сообщение roxblnfk » 2019.11.26, 14:43

mat.twg писал(а):
2019.11.25, 22:48
Конфиг в виде объекта можно запросить из контейнера.
Зачем? Конфиги лежат себе и лежат, трогать их без надобности не надо
Независимость от $params (или даже плагина composer-config-plugin) — можно конфижить вручную или через фабрики контейнера, создавая конфиг-объекты.
Тоже не ясно, какая зависимость от params? Ну есть params и есть... политика конфигов для приложения read only, не надо над ними никаких манипуляций кроме чтения...
Как раз о чтении. Как мы читаем конфиги? В common.php (web.php, console.php) пропихиваем $params в фабрику или в конструктор. Сей способ лично мне кажется хреновым. Куда проще указать в сигнатуре конфиговский класс, а контейнер его уже сам подпихнёт.
Почему хреновым:
Предположим, у нас есть сервис SomeService { __construct($config) {} }
Разработчику сервиса надо будет всего-то сделать следующее:

Код: Выделить всё

//params.php
return [...
  'SomeService' => [
    'param1' => 123,
    'param2' => 123, // это дефольные значения
  ]
...];
//common.php
return [...
  SomeService ::class => [
    '__construct()' => [$params['SomeService']]
  ]
...];
Дальше сервис развивается, и в конструктор уже надо передать пару зависимостей SomeService { __construct($config, Service2 $service2) {} }
Пока что в yii-di нет позиционных параметров, но они вроде как скоро появятся, но всё-равно, приходится править common.php

Код: Выделить всё

//common.php
return [...
  SomeService ::class => [
    '__construct()' => [$params['SomeService'], new Service2()]
  ]
...];
А поскольку Service2 может иметь свои зависимости или это вообще интерфейс (что скорее всего), то:

Код: Выделить всё

//common.php
return [...
  SomeService::class => function (Container $c) use (&$params) [
    $service2 = $c->get('Service2');
    return new SomeService($params['SomeService'], $service2);
  ]
...];
Подумаешь, разраб сервиса однажды заполнит common.php и все будут жить счастливо, никто ничего не заметит.
Но вот наступил момент, когда нам на проекте нужно использовать два инстанса сервиса SomeService с разными настройками. Что нам делать? Ползти в common.php, web.php и console.php (нужные подчеркнуть) этого сервиса и копировать в конфиг приложения правила для контейнера, заменяя там $params['SomeService'] на какой-нибудь $params['SomeService-2'].
Вспоминаем, что сигнатура класса может меняться (пахнет мажорной версией) и тогда при обновлении на новую версию придётся повторить эту вакханалию.

Если конфиг будет инстансом предопределённого конфиг-класса, то все эти инструкции в common.php не понадобятся (для случаев, если нужно только передать конфиг). Только для последнего случая нужно будет добавить правила для дублирующего сервиса и то только с указанием другого конфига.
mat.twg писал(а):
2019.11.25, 22:48
Валидацию или преобразование переданных значений можно производить прямо конфиг-классом. Этим также решиться проблема с описками в конфиге при его изменении. Сейчас описки проходят незамеченными, настройка откатывается к умолчанию.
php итак выкинет тебя с эксепшином при неверном ключе/значении в момент инициализации, зачем это масло масленное с вадидацией?
в момент инициализации не все сервисы могут подгрузиться, а значит не все конфиги могу быть заюзаны. Кроме того, в конфигурируемых сервисах не всегда может быть проверка на указание неиспользуемого ключа конфига. Предлагается бОльшая автоматизация, а не прыжки вручную от исключения к исключению с последующими спиритическими сеансами, когда исключения не вываливаются.
mat.twg писал(а):
2019.11.25, 22:48
Что-то так пока и не увидел профита, звучит как конфиги на моделях... сразу оговорюсь, я исключительно про концепт, абстрактно.
Тем не менее, большое спасибо за фидбек

mj4444
Сообщения: 41
Зарегистрирован: 2015.06.08, 19:56

Re: Концепт конфигов Yii3

Сообщение mj4444 » 2019.12.04, 06:25

roxblnfk писал(а):
2019.11.26, 14:43
Дальше сервис развивается, и в конструктор уже надо передать пару зависимостей
И чем вам тут конфиг-класс поможет?
Все те же самые проблемы останутся.

mj4444
Сообщения: 41
Зарегистрирован: 2015.06.08, 19:56

Re: Концепт конфигов Yii3

Сообщение mj4444 » 2019.12.04, 06:36

Однажды мне дали задачу проанализировать почему magento при всех включённых кешах медленно грузится и найти узкое место. Так вот оказалось что 50% всего времени PHP тратит на работу composer'а, который искал и подключал файлы с классами.

Конечно 1-10 таких лишних конфиг-классов погоды не сделают, но вот как тормозят 100 роутов в AppRouterFactory вы можете уже сейчас протестировать.

Выполнение функции и создание нового объекта в PHP операции достаточно быстрые.
НО!!! include PHP файла (особенно через composer) и создание объекта класса с зависимостями через DI вещи далеко не быстрые и как минимум в ядре и конфиге надо их стараться избегать.

mj4444
Сообщения: 41
Зарегистрирован: 2015.06.08, 19:56

Re: Концепт конфигов Yii3

Сообщение mj4444 » 2019.12.04, 06:42

На данный момент конфигурация приложений чересчур усложнена и далеко не каждому будет понятно что там и зачем вообще.
Учитывая что дефолтовый конфиг теперь будет у каждого модуля (плагина) свой и будет подключатся автоматически плагином composer-config-plugin, было бы здорово если бы новичкам было бы достаточно для подключения нового функционала выполнить "composer require" и добавить нужные настройки в params.php.

mj4444
Сообщения: 41
Зарегистрирован: 2015.06.08, 19:56

Re: Концепт конфигов Yii3

Сообщение mj4444 » 2019.12.04, 06:53

roxblnfk писал(а):
2019.11.20, 22:22
return $this->objAliases->get($alias, true);
На самом деле хотелось бы что бы на проде было минимум обращений к классу алиасов.
Было бы здорово если бы при кешировании конфигурации все алиасы (где это возможно) заменялись бы на реальные пути.

roxblnfk
Сообщения: 3
Зарегистрирован: 2019.11.20, 21:16

Re: Концепт конфигов Yii3

Сообщение roxblnfk » 2019.12.04, 17:24

mj4444 писал(а):
2019.12.04, 06:25
roxblnfk писал(а):
2019.11.26, 14:43
Дальше сервис развивается, и в конструктор уже надо передать пару зависимостей
И чем вам тут конфиг-класс поможет?
Все те же самые проблемы останутся.
Контейнер с автовайрингом сам подкинет нужные зависимости (в т.ч. конфиг)
mj4444 писал(а):
2019.12.04, 06:36
Однажды мне дали задачу проанализировать почему magento при всех включённых кешах медленно грузится и найти узкое место. Так вот оказалось что 50% всего времени PHP тратит на работу composer'а, который искал и подключал файлы с классами.

Конечно 1-10 таких лишних конфиг-классов погоды не сделают, но вот как тормозят 100 роутов в AppRouterFactory вы можете уже сейчас протестировать.

Выполнение функции и создание нового объекта в PHP операции достаточно быстрые.
НО!!! include PHP файла (особенно через composer) и создание объекта класса с зависимостями через DI вещи далеко не быстрые и как минимум в ядре и конфиге надо их стараться избегать.
Композер то чего плохого сделал? Какая разница, как будет реализована автозагрузка по PSR-4 (композером или своим кодом)?
Чтобы ускорить автозагрузку, в композере можно сгенерить карту классов, чтобы include был быстрее - есть opcache, а в php 7.4 предзагрузка. Не думаю, что тут стоит пенять на композер
А вот с DI согласен. В случае автовайринга рефлексия в рантайме добавляет оверхед. Но тут есть варианты решения, которые к релизу, я думаю, будут применены и дадут такой буст, что про медленный DI говорить не придётся (например есть некоторые варианты кеширования)
mj4444 писал(а):
2019.12.04, 06:42
На данный момент конфигурация приложений чересчур усложнена и далеко не каждому будет понятно что там и зачем вообще.
Учитывая что дефолтовый конфиг теперь будет у каждого модуля (плагина) свой и будет подключатся автоматически плагином composer-config-plugin, было бы здорово если бы новичкам было бы достаточно для подключения нового функционала выполнить "composer require" и добавить нужные настройки в params.php.
А оно так и предлагается, просто вместо ключей в params.php будет имя конфиг-класса
mj4444 писал(а):
2019.12.04, 06:53
roxblnfk писал(а):
2019.11.20, 22:22
return $this->objAliases->get($alias, true);
На самом деле хотелось бы что бы на проде было минимум обращений к классу алиасов.
Было бы здорово если бы при кешировании конфигурации все алиасы (где это возможно) заменялись бы на реальные пути.
Этот момент тоже уже обдуман и есть соображения по механизму подготовки значений для конфигов на этапе их кеширования. Тоже хочу максимально избежать зависимостей в конфигах, а также геттеров с логикой и сеттеров.

Ответить