Для тех, кому лень читать длинное предисловие: перемотайте до последней части «Простая идея, которая пришла мне в голову».
Я хотел поставить якорь, но хабрапарсер не разрешает :(
Официальная документация Zend Framework советует разделить конфигурационный файл на несколько секций, каждая из которых будет отвечать за разную среду, в которой должен работать проект.
При этом, одна секция конфига может наследовать другую, переопределяя только те параметры, которые должны быть изменены.
На первый взгляд, такая идея кажется разумной, но я столкнулся с некоторыми ограничениями этого подхода…
Вот пример конфигурационного файла из документации:
Итак, мы определяем полный список параметров для в секции production, а в секции staging переопределяем лишь несколько параметров для доступа к базе данных.
На практике, во-первых, хранить пароль доступа к продакшен БД в общем конфиге — не самая хорошая идея, а во-вторых, конфиг в таком виде в буквальном смысле невозможно хранить в мейнстримных системах контроля версий (СКВ) типа Git или Subversion.
Каждый разработчик, участвующий в проекте, с удовольствием будет коммитить собственные локальные настройки в секцию staging, в лучшем случае — создаст собственную секцию, отнаследуюя её от staging.
Всё это приводит к путанице и бесполезному разрастанию конфига в репозитории.
Чтобы общий конфиг не засорялся личными настройками девелоперов, обычно его называют как-то типа config.default.ini или config.ini.default, кладут в репозиторий, а затем каждый девелопер в своём рабочем каталоге делает его копию, которую называет config.ini и которую добавляет в список игнорируемых файлов СКВ.
Казалось бы, вот оно — счастье: один раз скопировал дефолтный конфиг, вбил туда личные настройки и забыл о нём навсегда…
Как бы не так!
Проходит месяц и кто-то из разработчиков решает добавить в кофиг какую-нибудь полезную (или не очень) настройку.
Он молча коммитит эту настройку в дефолтный конфиг, затем вносит изменения в код проекта, который полностью ломается, если в конфиге эта настройка не указанал, и с чувством выполненного долга уходит в отпуск…
Догадываетесь, что происходит на следующий день? :)
Даже если рассматривать самую утопическую ситуацию, когда изменивший конфиг девелопер не забывает отправить письмо в корпоративную рассылку с темой «Please update your local config.ini», а получившие это письмо разработчики не забывают его прочитать и, что немаловажно, осознать и выполнить — всё равно всем им приходится вручную обновлять свои локальные конфиги, порой используя утилиты типа diff, так как со временем конфиги только увеличиваются в размерах и следить за всеми изменениями вручную не удается.
Я решил расширить класс Zend_Config и реализовать в нём наследование конфигов.
Но как только я заглянул в его код, то сразу понял, что всё необходимое для наследования заложено в него изначально.
Итак, вот пример использования конфигов с наследованием.
Общий конфиг проекта — config.common.ini:
Мой личный конфиг — config.ini:
Пример реализации наследования конфигов:
Общий конфиг config.common.ini хранится в СКВ и любые новые настройки, которые добавляются в него, сразу же попадают ко всем разработчикам. В то же время, каждый разработчик может переопределить любую настройку в своём личном config.ini, который игнорируется СКВ.
Из минусов такого подхода могу отметить только то, что в личном конфиге приходится перечислять все секции, которые были объявлены в общем конфиге. Но лично для меня это не составляет особой проблемы, потому что их список меняется крайне редко, а чаще — никогда.
Разумеется, эта идея не нова и я точно помню, что где-то такой подход уже встречал.
Просто я очень удивился, что столь удобный прием не описан в официальной документации, а значит может быть неизвестен многим девелоперам.
UPD.
stfalcon подсказал, что нечто подобное уже реализовано в Zend_Application, начиная с версии 1.8.2.
Я хотел поставить якорь, но хабрапарсер не разрешает :(
Zend_Config и секции
Официальная документация Zend Framework советует разделить конфигурационный файл на несколько секций, каждая из которых будет отвечать за разную среду, в которой должен работать проект.
При этом, одна секция конфига может наследовать другую, переопределяя только те параметры, которые должны быть изменены.
На первый взгляд, такая идея кажется разумной, но я столкнулся с некоторыми ограничениями этого подхода…
Вот пример конфигурационного файла из документации:
; Production site configuration data
[production]
webhost = www.example.com
database.adapter = pdo_mysql
database.params.host = db.example.com
database.params.username = dbuser
database.params.password = secret
database.params.dbname = dbname
; Staging site configuration data inherits from production and
; overrides values as necessary
[staging : production]
database.params.host = dev.example.com
database.params.username = devuser
database.params.password = devsecret
Итак, мы определяем полный список параметров для в секции production, а в секции staging переопределяем лишь несколько параметров для доступа к базе данных.
Ограничения секционного подхода
На практике, во-первых, хранить пароль доступа к продакшен БД в общем конфиге — не самая хорошая идея, а во-вторых, конфиг в таком виде в буквальном смысле невозможно хранить в мейнстримных системах контроля версий (СКВ) типа Git или Subversion.
Каждый разработчик, участвующий в проекте, с удовольствием будет коммитить собственные локальные настройки в секцию staging, в лучшем случае — создаст собственную секцию, отнаследуюя её от staging.
Всё это приводит к путанице и бесполезному разрастанию конфига в репозитории.
Традиционные сценарии хранения конфига в СКВ
Чтобы общий конфиг не засорялся личными настройками девелоперов, обычно его называют как-то типа config.default.ini или config.ini.default, кладут в репозиторий, а затем каждый девелопер в своём рабочем каталоге делает его копию, которую называет config.ini и которую добавляет в список игнорируемых файлов СКВ.
Казалось бы, вот оно — счастье: один раз скопировал дефолтный конфиг, вбил туда личные настройки и забыл о нём навсегда…
Как бы не так!
Проходит месяц и кто-то из разработчиков решает добавить в кофиг какую-нибудь полезную (или не очень) настройку.
Он молча коммитит эту настройку в дефолтный конфиг, затем вносит изменения в код проекта, который полностью ломается, если в конфиге эта настройка не указанал, и с чувством выполненного долга уходит в отпуск…
Догадываетесь, что происходит на следующий день? :)
Даже если рассматривать самую утопическую ситуацию, когда изменивший конфиг девелопер не забывает отправить письмо в корпоративную рассылку с темой «Please update your local config.ini», а получившие это письмо разработчики не забывают его прочитать и, что немаловажно, осознать и выполнить — всё равно всем им приходится вручную обновлять свои локальные конфиги, порой используя утилиты типа diff, так как со временем конфиги только увеличиваются в размерах и следить за всеми изменениями вручную не удается.
Простая идея, которая пришла мне в голову
Я решил расширить класс Zend_Config и реализовать в нём наследование конфигов.
Но как только я заглянул в его код, то сразу понял, что всё необходимое для наследования заложено в него изначально.
Итак, вот пример использования конфигов с наследованием.
Общий конфиг проекта — config.common.ini:
[production]
resources.db.adapter = "PDO_MySQL"
resources.db.params.dbname = "system"
resources.db.params.username = "root"
resources.db.params.password = ""
phpSettings.display_startup_errors = 0
phpSettings.display_errors = 0
[development : production]
phpSettings.display_startup_errors = 1
phpSettings.display_errors = 1
Мой личный конфиг — config.ini:
[production]
[development : production]
resources.db.params.dbname = "system_laggyluke"
resources.db.params.username = "laggyluke"
resources.db.params.password = "mySecretPassword"
Пример реализации наследования конфигов:
// загружаем общий конфиг
// третий аргумент true означает, что конфиг не будет открыт в режиме read-only
$config = new Zend_Config_Ini('config.common.ini', 'development', true);
// проверяем, существует ли личный конфиг
if (file_exists('config.ini')) {
// если существует - загружаем его...
$configCustom = new Zend_Config_Ini('config.ini', 'development');
// ...и сливаем два конфига в один
$config->merge($configCustom);
}
// возвращаем конфиг в режим read-only, на всякий случай
$config->setReadOnly();
* This source code was highlighted with Source Code Highlighter.
Общий конфиг config.common.ini хранится в СКВ и любые новые настройки, которые добавляются в него, сразу же попадают ко всем разработчикам. В то же время, каждый разработчик может переопределить любую настройку в своём личном config.ini, который игнорируется СКВ.
Из минусов такого подхода могу отметить только то, что в личном конфиге приходится перечислять все секции, которые были объявлены в общем конфиге. Но лично для меня это не составляет особой проблемы, потому что их список меняется крайне редко, а чаще — никогда.
Разумеется, эта идея не нова и я точно помню, что где-то такой подход уже встречал.
Просто я очень удивился, что столь удобный прием не описан в официальной документации, а значит может быть неизвестен многим девелоперам.
UPD.
stfalcon подсказал, что нечто подобное уже реализовано в Zend_Application, начиная с версии 1.8.2.