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

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

Однако, мы подразумевали простейший вариант, когда система работает в единственной копии на сервере. Усложним положение:

Итак, бравые разработчики Вася, Петя, Миша и Гриша в поте лица разрабатывают очередную социальную сеть нового поколения. Каждый ведёт разработку в своей локальной версии на своём компьютере. Наработки они выкладывают на локальный тестовый сервер (где-нибудь в офисе в шкафу пылиться). Выкладывают, конечно, не по 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), но эту уже попахивает грязным хаком и никак не избавляет от второго минуса.

Второй минус: структура конфигурации у нас одна на всех. Если кто-то захочет добавить в неё какую-то новую секцию или изменить значение общего для всех параметра, ему придётся взять на себя функции системы контроля: «Эй, поцоны, а внесите себе такие-то изменения и на серверах не забудьте».

По-хорошему нам требуется разбить конфигурацию на две часть:

  1. Общая структура, одинаковая для всех, обрабатываемая системой контроля
  2. Локальные настройки, изменяющие нужные параметры из общей структуры

Слияние конфигов

Пример, как можно организовать данные требования.

Есть у нас базовый конфиг (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

Теперь конфигурации всех платформ лежат вместе и их не нужно игнорировать. При старте системы следует определить текущую платформу (об этом ниже) и использовать нужный файл.

Минус подхода: у Васи на локалке будет лежать конфигурация Пети. Минус довольно блёклый: с Васей из-за лишнего файла ничего не станет.

Плюсы:

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

Будем использовать второй подход.

Определение платформы

Итак, системе при запуске следует разобраться, а где собственно она запускается. У Миши, Гриши или где-то на продакшене.

В старые времена, когда я в одиночку разрабатывал какой-нибудь сайтик (жесть были, а не сайтики), делал примерно так (работал под 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');
}

И ещё много чего придумать можно.

Слишком много буков и спать уже пора. Продолжим в другой раз.

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

  • 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. Он качает РССку и показывает ее в админке. Модуль используется другими модулями.

    Sys_Notification/config.txt
      global.notification_urls: 'http://sys.rss'
    
    Kosteg_News/config.txt
      global.notification_urls: 'http://kosteg/news.rss'
    
    Kosteg_Shop/config.txt
      global.notification_urls: 'http://kosteg/shop.rss'
    

    конфиг мержится, и в итоге нужно получить все 3 ссылки на RSS, не заменяя их а как бы append’я.

    Костег, 30.01.2011, 19:19

  • Да, возможная ситуация. Хотя у меня не встечалась или как-то по иному решал.
    Впрочем, в рамки предложенного вполне попадает, немного алгоритм дополнить только нужно.

    vasa_c, 30.01.2011, 19:40

  • >Есть папка WINDOWS, значит это моя тачка, нет — сервер.
    Этим все сказано!

    Evgeniy, 14.06.2011, 18:49

  • Вот не могу понять как в моей ситуации лучше сделать.
    Тестовый сервер имеет свой .htaccess, продакшен — свой.
    Как такой случай можно обыграть?
    И есть ли какие нибудь средства таким образом манипулировать файлами?
    Заранее спасибо.

    Владимир, 7.03.2012, 1:34

  • Владимир, ну сначала нужно подумать почему htaccess разный, может можно сделать одинаковый.

    Ну а так — заигнорить его в системе контроля версий и иметь разные на разных платформах.

    Можно при деплое генерировать его на продакшене из конфигов, как здесь я примерно писал: http://blgo.ru/blog/2011/07/30/config-nginx/

    vasa_c, 7.03.2012, 12:45

Leave a comment