Конфигурация сайта 1: введение

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

Разобьём этот разговор на три части:

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


Итак, где же можно хранить настройки системы?

Нигде не хранить

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

mysql_connect('localhost', 'vasa', 'qwerty');

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

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

Глобальные переменные

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

$db_host     = 'localhost';
$db_user     = 'vasa';
$db_password = 'qwerty';
$db_dbname   = 'baza';
$site_url    = 'http://govnosait.org/mysite/';
$title       = 'Мой супер-пупер бложек';
$lang        = 'ru';
// ...

Уже лучше, но есть и минусы. Те же самые, что и вообще у глобальных переменных. Загадили глобальный контекст неоднородными, неструктурированными данными. С областями видимости будут постоянные проблемы. А так как и вся система при таком конфиге, очевидно, будет построена на глобальных переменных, то встаёт вопрос конфликта имён со всеми неприятно вытекающими последствиями.

Константы

define('DB_HOST', 'localhost');
define('DB_USER', 'vasa');
// ...

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

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

База данных

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

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

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

Такое решение обычно предполагается гибким, так как позволяет менять настройки через какую-нибудь админку, не имея доступа к коду (или не затрудняясь этим доступом). На деле для общих настроек это нахрен не нужно. Никому в жизни не понадобиться изменять путь к папке с темами для форума SMF. А если и понадобиться: ему всё равно придётся лезть в код и копировать эту папку на новое место.

Изменить что-то не предусмотренное в подобной админке, а тем более добавить новый параметр выливается во множество неприятных ощущений. А попытка скопировать себе сайт на локалку для разработке, превращается вообще во что-то мазохистское. После копирования нужно лезть в базу и выискивать там все параметры, которые необходимо изменить. Особенно радует, что хранятся они обычно в совершенно неудобоваримом формате.

Массивчик

$config = array(
    'db_host' => 'localhost',
    'db_user' => 'vasa',
    // ...
    'site_url' => 'http://govnosait.org/mysite/',
    'title'    => 'Мой супер-пупер бложек',
    'lang'     => 'ru',    
);

Теперь у нас всё в отдельном конфигурационном массиве и не засоряет глобальную область.

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

$config = array(
    'db' => array(
        'host' => 'localhost',
        'user' => 'vasa',
        // ...
    ),
    'site' => array(
        'url'   => 'http://govnosait.org/mysite/',
        'title' => 'Мой супер-пупер бложек',
    ),
    'lang' => 'ru',    
);

Теперь и глазу приятнее и можно работать не со всем массивом, а с его частями.

class DB {
    public function connect($params) {
        mysql_connect($params['host'], $params['user'], $params['password']);
        mysql_select_db($params['dbname']);
    }
    // ...
}
 
$db = new DB();
$db->connect($config['db']);

return array()

Не все знают, что обработку файла можно завершать, как и для функции, с помощью оператора return. Более того, из него можно возвращать значение, которое станет результатом include или require, которыми мы этот файл подключили.

Создадим файл конфигурации (config.php, допустим):

return array(
    'db' => array(
        'host' => 'localhost',
        'user' => 'vasa',
        // ...
    ),
    'site' => array(
        'url'   => 'http://govnosait.org/mysite/',
        'title' => 'Мой супер-пупер бложек',
    ),
    'lang' => 'ru',    
);

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

$config = include('/path/to/config.php'); // Считываем конфигурацию из файла в переменную

Интерфейс доступа

Напишем простейший класс для получения конфигурации:

class Config 
{
    public static function get() {
        if (!self::$config) {
            self::$config = include('/path/to/config.php');
        }
        return self::$config;
    }
 
    private static $config;
}

Его использование:

$config = Config::get(); // в нужном месте

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

Но гораздо важнее другое: мы разделили хранение конфигурации и интерфейс доступа к ней. Код, читающий конфигурацию, не обременяет себя знаниями о том, где она лежит, в каком виде и какими путями (может быть достаточно сложными) формируется. Можно полностью изменить способ хранения — на прикладной код это не повлияет.

С этого момента вопросами хранения конфига можно заниматься не задумываясь о вопросах его использования и наоборот.

Улучшение интерфейса

Интерфейс доступа к конфигу (в прошлом примере это Config::get()) можно улучшать бесконечно и по своему вкусу.

Убрать статику, а на объект навешать магических методов и SPL-интерфейсов, чтобы получилось что-то вроде:

$db_host = $config->db->host;
$db_user = $config->db->user;

Получение config-объекта можно сделать через IoC-контейнер или через какую-нибудь другую заумную вещь.

По поводу улучшения интерфейса можно придумать много чего, а ещё больше можно найти готового в интернете. Оставим эту тему и в дальнейшем сконцентрируемся сугубо на способе хранения конфигурации.

Форматы хранения: адаптеры

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

А вот для хранения могут быть удобны разные форматы. Опять-таки массив (как выше), xml, ini-файл или ещё что-то.

Так как хранение от представления мы уже отделили, хранить мы можем в чём угодно, никак не влияя этим на интерфейс. Единственное, что нужно, это преобразовывать выбранный формат хранения к древовидной структуре для доступа к ней (то есть использовать различные адаптеры для загрузки).

Один из примеров такого подхода: Zend_Config.

P.S. Что хранить в конфиге

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

Вот такого вот делать не надо:

$dir_root   = '/www/site.ru/htdocs';
$dir_image  = '/www/site.ru/htdocs/image';
$dir_thumbs = '/www/site.ru/htdocs/image/thumbs';
$dir_css    = '/www/site.ru/htdocs/css';
// и ещё десяток путей

Потребуется что-то изменить, перенести на другой сервер, скопировать на локалку: о такое можно убиться.

Все значения, которые можно сгенерировать на основании какого-то базового, лучше генерировать:

$dir_root   = '/www/site.ru/htdocs';
$dir_image  = $dir_root.'/image';    // Или шаблон вида "{{ root }}/image", который вычислять в нужном месте.
$dir_thumbs = $dir_image.'/thumbs';
$dir_css    = $dir_root.'/css';

Базовое значение в примере, также в большинстве случаев можно получить автоматически на основании $_SERVER['DOCUMENT_ROOT'] или dirname(__FILE__).

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

Для начала, пожалуй хватит. В следующей части поговорим о более интересных вещах.

Конфигурация сайта 2.1: совместная разработка (платформы)

18 комментариев »

  • В питоне все прикольнее, например имеем settings.py:
    DATABASE_NAME = ‘name’
    DATABASE_USERNAME= ‘username’
    DATABASE_USERNAME= ‘password’

    и юзаем где надо:
    import settings
    или
    from settings import DATABASE_NAME, DATABASE_USERNAME
    и т.д.

    Мне такой способ хранения конфигов куда больше нравится :)

    adw0rd, 25.01.2011, 0:51

  • Не вижу особой разницы, если честно.
    А в следующей статье напишу вещи чуть посложнее, расскажешь как такое в питоне сделать.

    vasa_c, 25.01.2011, 11:31

  • Я о том, что не надо никаких зендконфигов и прочей мутни… И это не (супер)глобальные переменные и т.д.

    adw0rd, 25.01.2011, 13:06

  • Прижал, спасибо

    phpdude, 26.01.2011, 5:02

  • Спасибо, порадовал. По большей части так и делаю, видимо я на правильном пути :) .

    artoodetoo, 26.01.2011, 7:18

  • phpdude, чего прижал? Поржал?

    artoodetoo, я в тебе и не сомневался :)

    vasa_c, 26.01.2011, 10:12

  • интересно как хранить настройки для всего сайта и реврайтить некоторые для поддоменов, например…?

    kostyl, 26.01.2011, 15:31

  • Бля, Вася_ц, пароль «qwerty» — это же просто трындец, сайты твои поломают на раз-два-три. Поменяй на что-то путевое!

    Givi, 26.01.2011, 16:09

  • Givi, сломай, дам пирожок )

    vasa_c, 26.01.2011, 16:29

  • >интересно как хранить настройки для всего сайта и реврайтить некоторые для поддоменов, например…?
    не понял

    vasa_c, 26.01.2011, 16:29

  • >не понял
    ну заходит юзверь на поддомен, а там функционал на другой СУБД висит…

    kostyl, 26.01.2011, 18:24

  • kostyl, в следующей статейке четай.

    vasa_c, 26.01.2011, 18:27

  • vasa_c,
    Вы рекомендуете использовать формат return array( … );
    и далее пишите «Все значения, которые можно сгенерировать на основании какого-то базового, лучше генерировать:»
    Подскажите как объединить выше написанное?
    На примере следующего файла конфигурации:
    $CONFIG = array(
    ‘a’ => «1»,
    ‘b’ => «2»,
    ‘ab’ => $CONFIG[‘a’].$CONFIG[‘b’],
    );

    Кирилл, 19.04.2011, 0:05

  • Кирилл,

    1. Полухак:

    $tmp = 'base';
    retrun array(
    'a' => $tmp.'a',
    'b' => $tmp.'b',
    );

    2. В случае с путями, обычно получается построить вокруг __DIR__.

    3. Я же люблю шаблоны:

    return array(
    'a' => 1,
    'b' => 2,
    'ab' => '{{ a }}-{{ b }}',
    );

    В коде уже обрабатывать.

    vasa_c, 19.04.2011, 21:59

  • vasa_c, спасибо за идеи! Для моей текущей задачи воспользуюсь полухаком.

    Кирилл, 19.04.2011, 23:45

  • Я тоже часто пользуюсь полухаками, благо до return можно производить работу…

    kostyl, 20.04.2011, 15:32

  • Не ожидал увидеть такого слаженного решения рассматриваемого вопроса.
    Автору респект. :)

    Помощь по php, 26.05.2011, 14:10

  • Хороша ідея, відмовився від define!

    Володимир, 17.04.2013, 12:20

Leave a comment