В прошлой статье мы разобрались, как можно хранить конфигурацию системы и как её можно использовать.
Однако, мы подразумевали простейший вариант, когда система работает в единственной копии на сервере. Усложним положение:

Итак, бравые разработчики Вася, Петя, Миша и Гриша в поте лица разрабатывают очередную социальную сеть нового поколения. Каждый ведёт разработку в своей локальной версии на своём компьютере. Наработки они выкладывают на локальный тестовый сервер (где-нибудь в офисе в шкафу пылиться). Выкладывают, конечно, не по FTP, а с использованием какой-нибудь системы контроля версия, например, Mercurial.
Ведущий программист проверяет обновления на наличие ошибок и в случае чего даёт по шее. Если же ошибок не видно и локальная версия достигает какого-то стабильного состояния: все изменения отправляются уже на боевой сервер. Вернее на сервера, потому что проект крупный и серверов уже аж три штуки стоит.
Итого, система работает уже не в одной копии, а в восьми (4 разработчика, локальный сервер, 3 рабочих сервера). Назовём то, где работает конкретная копия системы платформой и дадим каждой имя (платформа server.1, платформа vasya, платформа local и т.д.).
Понятно, что конфигурация системы будет иметь одинаковую структуру для всех платформ, но вот на каждой конкретной значения параметров могут отличаться. То есть, у каждого из разработчиков могут быть свои параметры локальной БД. Или Вася захотел включить у себя вывод отладочной информации, а Петя этого не захотел. И так далее.
Подумаем, как это можно организовать.
Изменения одного файла
У нас есть конфигурационный файл (config.php) из предыдущей статьи:
return array( 'db' => array( 'host' => 'localhost', 'user' => 'vasa', // ... ), 'debug' => false, // ... );
Будем рассматривать такой формат хранения. При использовании XML или INI-файлов ничего в самой сути не изменится.
У каждого из разработчиков, допустим, могут быть свои настройки локальной БД. Можно заставить всех создать у себя базу с одинаковыми настройками. Но, во-первых, БД тут просто для примера. Во-вторых, на рабочих серверах в качестве хоста будет явно не «localhost».
Что делать? Простейшее решение: брать, да прямо в своём локальном config.php писать свои настройки. Ещё кто-то должен вписать настройки в конфиги на серверах.
Первый минус такого подхода: у нас система контроля версий, вследствии чего будут постоянные конфликты. Можно заигнорить этот файл (.hgignore в Mercurial), но эту уже попахивает грязным хаком и никак не избавляет от второго минуса.
Второй минус: структура конфигурации у нас одна на всех. Если кто-то захочет добавить в неё какую-то новую секцию или изменить значение общего для всех параметра, ему придётся взять на себя функции системы контроля: «Эй, поцоны, а внесите себе такие-то изменения и на серверах не забудьте».
По-хорошему нам требуется разбить конфигурацию на две часть:
- Общая структура, одинаковая для всех, обрабатываемая системой контроля
- Локальные настройки, изменяющие нужные параметры из общей структуры
Слияние конфигов
Пример, как можно организовать данные требования.
Есть у нас базовый конфиг (config.php):
return array( 'db' => array( 'host' => '192.168.0.27:3307', 'username' => 'user', 'password' => 'password', 'dbname' => 'db', ), 'debug' => false, 'texts' => array( 'title' => 'Супер-пупер социальная сеть!', 'copy' => '© Мега-пупер программисты, 2002-2011'; ), );
И локальный конфиг у разработчика Васи (local.php):
return array( 'db' => array( 'host' => 'localhost', ), 'debug' => true, );
При загрузке локальный сливается с базовым. Указанные в локальном параметры перекрывают базовые, не указанные остаются прежними, массивы сливаются рекурсивно. И, в конце концов, получается массив с которым уже работает система на локалке у Васи:
return array( 'db' => array( 'host' => 'localhost', // Перекрыт 'username' => 'user', 'password' => 'password', 'dbname' => 'db', ), 'debug' => true, // Перекрыт 'texts' => array( 'title' => 'Супер-пупер социальная сеть!', 'copy' => '© Мега-пупер программисты, 2002-2011'; ), );
base+local vs платформы
И здесь вновь несколько вариантов, как всё организовать.
Можно, как в прошлом пункте, держать два файла base.php и local.php. Локальный будет у каждого свой и его следует заигнорировать в системе контроля версий.
А можно по другому. Вспомним, что все наши машины мы назвали платформами и дали им имена. Создадим для каждой из них свой файл:
config.php # базовый vasya.php petya.php misha.php grisha.php test.php server1.php server2.php server3.php
Теперь конфигурации всех платформ лежат вместе и их не нужно игнорировать. При старте системы следует определить текущую платформу (об этом ниже) и использовать нужный файл.
Минус подхода: у Васи на локалке будет лежать конфигурация Пети. Минус довольно блёклый: с Васей из-за лишнего файла ничего не станет.
Плюсы:
- Если Вася ведущий программист, то ему, наоборот, не помешает посмотреть, чего-там Петя напортачил.
- Конфиги удалённых серверов можно править у себя и просто коммитить, а не лезть на сами сервера.
- Ещё несколько более важных плюсов, которые рассмотрим далее.
Будем использовать второй подход.
Определение платформы
Итак, системе при запуске следует разобраться, а где собственно она запускается. У Миши, Гриши или где-то на продакшене.
В старые времена, когда я в одиночку разрабатывал какой-нибудь сайтик (жесть были, а не сайтики), делал примерно так (работал под Win):
define('PLATFORM_NORMAL', !file_exists('C:\Windows')); $config = array( 'db_host' => PLATFORM_NORMAL ? 'mysql.hosting.ru' : 'localhost', 'db_user' => PLATFORM_NORMAL ? 's3298e34r4urn' : 'test', // ... );
Есть папка WINDOWS, значит это моя тачка, нет — сервер.
А теперь мы сохраним суть, но попробуем сделать менее стрёмно. Всё что нам по-идее нужно, это метод, определяющий платформу.
public function definePlatform() { if (file_exists('/home/vasya')) { return 'vasya'; } elseif (file_exists('/home/petya')) { return 'petya'; } elseif { // ... } }
Ещё менее стрёмно, использовать имя машины.
Ещё лучше просто использовать файл platform.php (выше корня системы или заигнорированный), примерно такого содержания:
return 'vasya';
Определение платформы в итоге:
public function definePlatform() { return include(__DIR__.'/../platform.php'); }
И ещё много чего придумать можно.
Слишком много буков и спать уже пора. Продолжим в другой раз.









file_exists(‘C:\Windows’));
у меня винда на диске E и папка называется WIN.0
это мелочи :)
видал огромный свич с именами машин :)
( IvanSCM, 26.01.2011, 15:27 )
Иногда у нас значение ключа конфига – массив. Тут нужно по-хитрому мержить.
( Костег, 30.01.2011, 18:14 )
>Иногда у нас значение ключа конфига – массив. Тут нужно по-хитрому мержить.
Не совсем понимаю о чём ты. Приведи пример.
( vasa_c, 30.01.2011, 18:47 )
'host' => 'localhost', // ПерекрытСейчас не могу придумать пример, но иногда бывает нужно подставить не скалляр (‘localhost’) а какой-нить список. Ессно не список хостов, ну например каких-нибудь кеширующих драйверов. Ну так вот, нужно придумать какие-то директивы, когда дочерний список будет перекрывать родительский, а когда будет сливаться с ним
( Костег, 30.01.2011, 19:04 )
Ну, в принципе «db» и есть список, где «host» элемент. Можно и «host» списком сделать.
У меня массивы мержатся. Те ключи которые есть, перекрывают старые. Старые, которых не перекрыли остаются (см. статью). Пока хватало такого поведения.
А что ещё можно придумать интересного?
( vasa_c, 30.01.2011, 19:10 )
я придумал пример. У нас есть модуль Sys_Notification. Он качает РССку и показывает ее в админке. Модуль используется другими модулями.
конфиг мержится, и в итоге нужно получить все 3 ссылки на RSS, не заменяя их а как бы append’я.
( Костег, 30.01.2011, 19:19 )
Да, возможная ситуация. Хотя у меня не встечалась или как-то по иному решал.
Впрочем, в рамки предложенного вполне попадает, немного алгоритм дополнить только нужно.
( vasa_c, 30.01.2011, 19:40 )
>Есть папка WINDOWS, значит это моя тачка, нет — сервер.
Этим все сказано!
( Evgeniy, 14.06.2011, 18:49 )