Коротко об архитектуре компонента Symfony Config
Компонент Symfony 2 Config предназначен для работы с конфигурационными файлами и предоставляет следующие возможности:
- Поддержка древовидной структуры конфигурации
- Абстракция составных частей конфигурации, из которых производится ее загрузка (ресурсы, загрузчики ресурсов и т.д.)
- Поддержка произвольного количества составных частей конфигурации и некоторых правил по сборке и объединению
- Кеширование прочитанной конфигурации и автоматическая ее пересборка при изменении одного из исходных файлов
- Валидация конфигурации по различным правилам и подробная информация об ошибках парсинга
Официальная документация по этому компоненту содержит подробную информацию по его использованию. А мы давайте посмотрим на то, как устроен этот компонент внутри.
Определение структуры конфигурации
Типы ключей конфигурации
Вот так выглядит диаграмма классов, которые описывают структуру конфигурации.
Назначение практически всех классов понятно из их названия. Отмечу только, что для построения дерева конфигурации используется нода
ArrayNode
. Если требуется, чтобы внутри ArrayNode
размещались не просто предпоределенные ноды, а несколько других ArrayNode
, но с четко одинаковой предопределенной внутренней структурой, можно использовать PrototypedArrayNode
.Для построения описания конфигурации используется класс Symfony\Component\Config\Definition\Builder\TreeBuilder
примерно вот таким способом:
root('acme_demo');
$rootNode
->children()
->arrayNode('entities')
->addDefaultsIfNotSet()
->prototype('scalar')->end()
->defaultValue(
array(
'Acme\BaseBundle\Entity\DefaultEntity1',
'Acme\BaseBundle\Entity\DefaultEntity2',
)
)
->end();
return $rootNode;
}
}
Структуру конфигурации не обязательно объявлять всю целиком в одном месте. Можно сделать это частями, а затем объединить части при помощи метода
append
у NodeBuilder
.Нормализация
Нормализацией называется приведение имен ключей нод и их значений, если потребуется, к каноническому виду. Фактически, сейчас нормализация используется только для того, чтобы привести ноды, описанные в xml в виде
Значение потомка
к виду
"children" => Array(
[0] => "Значение потомка"
)
Для нормализации нод вызывается метод
normalize()
из Symfony\Component\Config\Definition\NodeInterface
. А кроме того, у Symfony\Component\Config\Definition\BaseNode
есть еще метод preNormalize
. Последний используется для приведения к общему виду ключей типа foo_bar
и foo-bar
.Финализация
Процесс финализации ноды выполняет действия, по подготовке ноды к чтению внутри конфигурации и проверки на соответствие заявленому типу и его правилам. Финализация выполянется методом
finalizeValue
потомков BaseNode
Валидация данных выполняется как с помощью предопределенных методов NodeDefinition
и его потомков вроде isRequired
, так и с помощью расширенной валидации, делегированной классу Symfony\Component\Config\Definition\Builder\ValidationBuilder
.
Правила объединения данных из нескольких частей содержатся в классе Symfony\Component\Config\Definition\Builder\MergeBuilder
. Делегирование ему проверок выполняется методом merge () класса NodeDefinition
. Например, можно запретить переопределять значение выбранного ключа конфигурации другими конфигурационными файлами после того, как он был прочитан в первый раз.
Сам процесс валидации / нормализации / финализации конфигурации выглядит так:
$configs = array($config1, $config2); //Загруженные любым способом части конфигурации
$processor = new Processor(); // Процессор конфигурации
$configuration = new Configuration(); // Класс Configuration c правилами проверки (см. выше).
$processedConfiguration = $processor->processConfiguration(
$configuration,
$configs
);
Билдер
Как нетрудно заметить, для самого процесса построения описания конфигурации
TreeBuilder
использует экземпляр класса Symfony\Component\Config\Definition\Builder\NodeBuilder
. Поэтому вы вполне можете определять свои типы нод для конфигурации. Для этого необходимо создать свой вариант реализации NodeInterface
и своего потомка \Symfony\Component\Config\Definition\Builder\NodeDefinition
. После чего просто вызвать метод setNodeClass
у NodeBuilder
.Во всех подробностях процесс определения структуры конфигурации описан тут.
Дампер
После того, как структура конфигурации построена, ее можно сдампить с помощью различных дамперов из пространства имен
Symfony\Component\Config\Definition\Dumper
. Сейчас там есть два варианта: YamlReferenceDumper
и XmlReferenceDumper
. Эти дамперы используются, например, когда вы вызываете с консоли ./bin/symfony config:dump-reference
(см. Symfony\Bundle\FrameworkBundle\Command\ConfigDumpReferenceCommand
)Загрузка конфигурацииРесурсы и загрузчики
- Части конфигурации в Symfony описываются ресурсами (
Symfony\Component\Config\Resource\ResourceInterface
). Понятие ресурса достаточно абстрактно. Им может быть как файл, так и любой другой источник данных. Например, таблица БД или поле в ней. - Мониторинг ресурсов на наличие изменений в них ведут инспекторы ресурсов (
Symfony\Component\Config\ResourceCheckerInterface
). - Загрузку конфигурации из ресурсов выполняют загрузчики (
Symfony\Component\Config\Loader\LoaderInterface
). - Поиск подходящего загрузчика для ресурса выполняют ресолверы (
Symfony\Component\Config\Loader\LoaderResolverInterface
). Symfony\Component\Config\Loader\DelegatingLoader
позволяет загрузить ресурс, автоматически найдя необходимый загрузчик, перебирая массив переданных ресолверов.- Размещать конфигурационные файлы можно в различных папках. Поиск файлов в них можно вести с помощью
Symfony\Component\Config\FileLocator
Нужно сказать, что сам компонент Config не содержит конкретных реализаций загрузчиков. Он лишь предоставляет необходимые интерфейсы для их реализации. Причем способ загрузки и целевой контейнер для загруженных данных тоже не регламентирован. Если посмотреть на реализацию
Symfony\Component\DependencyInjection\Loader\YamlFileLoader
, то видно, что конфигурация загружается прямо в контейнер.Кеширование конфигурации
Symfony Config позволяет кешировать загруженную конфигурацию с помощью класса
Symfony\Component\Config\ConfigCache
: isFresh()) {
$configFiles = []; // Здесь имена файлов, из которых состоит конфигурация
$resources = array();
foreach ($configFiles as $cfgFile) {
// Здесь загружаем конфигурацию
// .....
// И добавляем ресурс в массив
$resources[] = new FileResource($cfgFile);
}
$code = '...'; //Здесь строим кэш из загруженных данных
//Пишем кеш. Рядом с файлом кеша запишется файл с метаданными со списком исходных ресурсов
$cacheFile->write($code, $resources);
}
// Подключаем файл кеша
require $cachePath;
Можно инкапсулировать алгоритм перестройки кеша, например, в класс, а затем воспользоваться
Symfony\Component\Config\ConfigCacheFactory
вместо ConfigCache
для дальнейшей работы. ConfigCacheFactory
принимает в конструкторе callable
, который будет перестраивать кеш.Пример использования компонентаКомпонент Symfony Config вполне можно использовать и без фреймворка. В качестве примера приведу небольшой кусочек кода, написанный уважаемым magickatt:
processConfiguration(
$yamlConfiguration,
array($configuration) // Здесь может быть любое количество *.yml файлов
);
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
class Configuration
{
/**
* @return TreeBuilder
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('arbitary');
$rootNode->children()
->scalarNode('host')
->isRequired()
->cannotBeEmpty()
->end()
->scalarNode('username')
->isRequired()
->cannotBeEmpty()
->end()
->scalarNode('password')
->isRequired()
->cannotBeEmpty()
->end()
->booleanNode('bindRequiresDn')
->defaultTrue()
->end();
return $treeBuilder;
}
}