воскресенье, 20 декабря 2009 г.

Конфигурация системы. Класс работы с настройками.

Любая система работает с некоторыми настройками. Если Ваша система не имеет параметров — она деревянная.
  • Ядру CMS нужны настройки;
  • Настройки нужно где-то хранить;
  • Настройки должны иметь удобочитаемый формат для правки, в экстренных случаях, руками;
  • Кроме ядра, параметры могут иметь и каждый модуль CMS;
  • Каждый имеющий параметры модуль, не должен заботиться о том, в каком виде хранятся параметры и где они хранятся;
  • Ядро должно предоставлять строго определенный интерфейс для работы с параметрами любого модуля и отдельно — работу со своими параметрами (на случай модуля настройки ядра).

Спасибо, КЭП.

Основные моменты ясны. Теперь нужно обсудить решение.

Давайте определимся с форматом.
  • Обычный php-файл где наполняется ассоциативный массив.
    Кажется идеально — легко правиться, наглядно, очень быстро понимается сервером.
    Главный минус — в такой фал проблематично писать. Нужен код для генерации подобных файлов. Задача не слишком сложная — читаем файл обычным инклудом и при записи просто разбираем массив и записываем в нужном формате.
    • Красиво? Да.
    • Безопасно? НЕТ!
      В таком файле конфигурации можно разместить вредоносной код и он будет выполнен со всеми правами серверного приложения. Способов проникновения в такой файл много. Это могут быть атаки на не экранированные параметры или использование других ошибок, все случаи предусмотреть не получиться. Важно исключить саму возможность выполнения такого кода.
  • XML
    Хороший формат. Структурированное хранение информации. Вполне достаточная гибкость хранения сложных структур данных. Популярны, в конце концов, формат.
  • INI
    Динозавр среди форматов. В нашем случае подходит плохо, т.к. он отлично справляется только с хранением двухуровневых данных. Да, можно научить его хранить и более сложные структуры. Но... Это лишнее.
  • Сериализация данных средствами PHP
    Чтож, это вариант. PHP способен сериализировать большие объемы данных в формат удобный для него. Вы когда ни будь пытались прочитать такие данные?
    Пример:
    Имеем массив:
    array('conf_id' => 34, 'size_mas' => array(123, 12, 34, 56));
    в сериализированом виде он выглядит как:
    a:2:{s:7:"conf_id";i:34;s:8:"size_mas";a:4:{i:0;i:123;i:1;i:12;i:2;i:34;i:3;i:56;}}
    Вам легко такое читать и изменять? Мне — нет.
  • Свои форматы
    Это хорошо. Если Вы имеете свою библиотеку позволяющую сохранять в файлы и читать сложные структуры данных. И Вас устраивает вид в котором они хранятся — это Ваш выбор.
  • YAML
    Этот формат я выбрал для себя. Меня устраивает то, как данные выглядят в файле.
    Тот же массив из примера:
    conf_id: 34
    size_mas: 
      - 123
      - 12
      - 34
      - 56
    
Возможно, многие из Вас могут привести еще массу популярных форматов. Я перечислил лишь основные для меня. Формат, в нашем случае, это контейнер. Важно, что бы он был удобен для Вас. Я не хочу развивать холиваров по поводу отказа от XML — ну не лежит у меня к нему душа, слишком много накладных расходов.

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

Класс для работы с параметрами в моем решении представляет из себя класс с набором статических методов:
  • load($module, $folder = null, $file = null) — загрузка
    $module — имя модуля чьи параметры мы хотим прочитать или константа чтения параметров ядра (например, «_SYSTEM_»).
    $folder — имя папки хранения файла конфигурации. Может использоваться для отделения специфических настроек модуля.
    $file — имя файла для специфических параметров.
  • save($module, $array, $folder = null, $file = null) — сохранение
    единственное отличие от load — второй параметр, принимающий массив с параметрами.
  • toString($data) — возвращает текст сериализованых данных $data
  • fromString($str) — обратная операция, из текста в массив.
Реализация данного класса для выбранного формата хранения своя. Здесь Вы вольны реализовывать как Вам больше нравится.

Что именно использовать для параметров — массив или объект — решать Вам. Не сложно реализовать и обработку объектов. Для простоты описания я выбрал простые массивы.

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

В предыдущей заметке я описал свою файловую структуру и по ней путь к файлу строиться как mods_vars/$module/dynconf/[$folder/][$file] или с использованием стандартного имени conf.yaml.

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

Здесь, нужно определиться с именем такого класса. Имя Config нам не доступно, т.к. совпадает с классом фреймверка. Да, базовый класс фреймверка называеться CI_Config, но я бы советовал выбрать другое имя. И вот еще что, его можно было бы вписать в расширение базового класса через механизм «MY_class», но я не стал этого делать, т.к. в моем понимании это отчасти противоречит идеологии фреймверка в связке с ядром. Я предпочел сделать отдельный класс с именем Confman. Теперь вызов параметров модуля admin у меня выглядет как
$conf = Confman::load('admin');

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

Методы позволяющие заполнить поля в классе из конфига я решил исключить. Но если кому-то это покажется нужным, то знание языка Вам в помощь.



Если я что-то упустил — добро пожаловать в комментарии, буду рад ответить на вопросы и дополнить статью, если я о чем-то не вспомнил.

2 комментария:

Gothic комментирует...

Было бы интересно посмотреть на реализацию Вашего класса на примере.

Unknown комментирует...

Вы не знаете как написать свой?
Мне кажется, это простая задача.

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

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

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