Коротко об архитектуре компонента Symfony Config

в 10:36, , рубрики: php, symfony, symfony config, symfony2

Коротко об архитектуре компонента Symfony Config - 1

Компонент Symfony 2 Config предназначен для работы с конфигурационными файлами и предоставляет следующие возможности:

  • Поддержка древовидной структуры конфигурации
  • Абстракция составных частей конфигурации, из которых производится ее загрузка (ресурсы, загрузчики ресурсов и т.д.)
  • Поддержка произвольного количества составных частей конфигурации и некоторых правил по сборке и объединению
  • Кеширование прочитанной конфигурации и автоматическая ее пересборка при изменении одного из исходных файлов
  • Валидация конфигурации по различным правилам и подробная информация об ошибках парсинга

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

Определение структуры конфигурации

Типы ключей конфигурации

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

Коротко об архитектуре компонента Symfony Config - 2

Назначение практически всех классов понятно из их названия. Отмечу только, что для построения дерева конфигурации используется нода ArrayNode. Если требуется, чтобы внутри ArrayNode размещались не просто предпоределенные ноды, а несколько других ArrayNode, но с четко одинаковой предопределенной внутренней структурой, можно использовать PrototypedArrayNode.

Для построения описания конфигурации используется класс SymfonyComponentConfigDefinitionBuilderTreeBuilder примерно вот таким способом:

<?php

use SymfonyComponentConfigDefinitionBuilderTreeBuilder;
use SymfonyComponentConfigDefinitionConfigurationInterface;

class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('acme_demo');

        $rootNode
            ->children()
            ->arrayNode('entities')
            ->addDefaultsIfNotSet()
            ->prototype('scalar')->end()
            ->defaultValue(
                array(
                    'AcmeBaseBundleEntityDefaultEntity1',
                    'AcmeBaseBundleEntityDefaultEntity2',
                )
            )
            ->end();

        return $rootNode;
    }
}

Структуру конфигурации не обязательно объявлять всю целиком в одном месте. Можно сделать это частями, а затем объединить части при помощи метода append у NodeBuilder.

Нормализация

Нормализацией называется приведение имен ключей нод и их значений, если потребуется, к каноническому виду. Фактически, сейчас нормализация используется только для того, чтобы привести ноды, описанные в xml в виде

<children>
    <child>Значение потомка</child>
</children>

к виду

    "children" => Array(
        [0] => "Значение потомка"
    )

Для нормализации нод вызывается метод normalize() из SymfonyComponentConfigDefinitionNodeInterface. А кроме того, у SymfonyComponentConfigDefinitionBaseNode есть еще метод preNormalize. Последний используется для приведения к общему виду ключей типа foo_bar и foo-bar.

Финализация

Процесс финализации ноды выполняет действия, по подготовке ноды к чтению внутри конфигурации и проверки на соответствие заявленому типу и его правилам. Финализация выполянется методом finalizeValue потомков BaseNode

Валидация данных выполняется как с помощью предопределенных методов NodeDefinition и его потомков вроде isRequired, так и с помощью расширенной валидации, делегированной классу SymfonyComponentConfigDefinitionBuilderValidationBuilder.

Правила объединения данных из нескольких частей содержатся в классе SymfonyComponentConfigDefinitionBuilderMergeBuilder. Делегирование ему проверок выполняется методом merge() класса NodeDefinition. Например, можно запретить переопределять значение выбранного ключа конфигурации другими конфигурационными файлами после того, как он был прочитан в первый раз.

Сам процесс валидации / нормализации / финализации конфигурации выглядит так:

$configs = array($config1, $config2); //Загруженные любым способом части конфигурации

$processor = new Processor(); // Процессор конфигурации
$configuration = new Configuration(); // Класс Configuration c правилами проверки (см. выше).
$processedConfiguration = $processor->processConfiguration(
    $configuration,
    $configs
);

Билдер

Как нетрудно заметить, для самого процесса построения описания конфигурации TreeBuilder использует экземпляр класса SymfonyComponentConfigDefinitionBuilderNodeBuilder. Поэтому вы вполне можете определять свои типы нод для конфигурации. Для этого необходимо создать свой вариант реализации NodeInterface и своего потомка SymfonyComponentConfigDefinitionBuilderNodeDefinition. После чего просто вызвать метод setNodeClass у NodeBuilder.

Во всех подробностях процесс определения структуры конфигурации описан тут.

Дампер

После того, как структура конфигурации построена, ее можно сдампить с помощью различных дамперов из пространства имен SymfonyComponentConfigDefinitionDumper. Сейчас там есть два варианта: YamlReferenceDumper и XmlReferenceDumper. Эти дамперы используются, например, когда вы вызываете с консоли ./bin/symfony config:dump-reference (см. SymfonyBundleFrameworkBundleCommandConfigDumpReferenceCommand)

Загрузка конфигурации

Ресурсы и загрузчики

  • Части конфигурации в Symfony описываются ресурсами (SymfonyComponentConfigResourceResourceInterface). Понятие ресурса достаточно абстрактно. Им может быть как файл, так и любой другой источник данных. Например, таблица БД или поле в ней.
  • Мониторинг ресурсов на наличие изменений в них ведут инспекторы ресурсов (SymfonyComponentConfigResourceCheckerInterface).
  • Загрузку конфигурации из ресурсов выполняют загрузчики (SymfonyComponentConfigLoaderLoaderInterface).
  • Поиск подходящего загрузчика для ресурса выполняют ресолверы (SymfonyComponentConfigLoaderLoaderResolverInterface).
  • SymfonyComponentConfigLoaderDelegatingLoader позволяет загрузить ресурс, автоматически найдя необходимый загрузчик, перебирая массив переданных ресолверов.
  • Размещать конфигурационные файлы можно в различных папках. Поиск файлов в них можно вести с помощью SymfonyComponentConfigFileLocator

Нужно сказать, что сам компонент Config не содержит конкретных реализаций загрузчиков. Он лишь предоставляет необходимые интерфейсы для их реализации. Причем способ загрузки и целевой контейнер для загруженных данных тоже не регламентирован. Если посмотреть на реализацию SymfonyComponentDependencyInjectionLoaderYamlFileLoader, то видно, что конфигурация загружается прямо в контейнер.

Кеширование конфигурации

Symfony Config позволяет кешировать загруженную конфигурацию с помощью класса SymfonyComponentConfigConfigCache:

<?php

use SymfonyComponentConfigConfigCache;
use SymfonyComponentConfigResourceFileResource;

$cachePath = __DIR__.'/cache/appSomeCacheFile.php';

// Режим отладки определяет, будут ли проверяться на изменения ресурсы, из которых строился кеш
$cacheFile = new ConfigCache($cachePath, true);

if (!$cacheFile->isFresh()) {
    $configFiles = []; // Здесь имена файлов, из которых состоит конфигурация

    $resources = array();
    foreach ($configFiles as $cfgFile) {
        // Здесь загружаем конфигурацию
        // .....
        // И добавляем ресурс в массив
        $resources[] = new FileResource($cfgFile);
    }

    $code = '...'; //Здесь строим кэш из загруженных данных

    //Пишем кеш. Рядом с файлом кеша запишется файл с метаданными со списком исходных ресурсов
    $cacheFile->write($code, $resources);
}

// Подключаем файл кеша
require $cachePath;

Можно инкапсулировать алгоритм перестройки кеша, например, в класс, а затем воспользоваться SymfonyComponentConfigConfigCacheFactory вместо ConfigCache для дальнейшей работы. ConfigCacheFactory принимает в конструкторе callable, который будет перестраивать кеш.

Пример использования компонента

Компонент Symfony Config вполне можно использовать и без фреймворка. В качестве примера приведу небольшой кусочек кода, написанный уважаемым magickatt:

<?php
// Загружаем специфичную для приложения конфигурацию
try {
    $basepath = __DIR__ . '/config';
    $configuration = Yaml::parse($basepath . '/config.yml');
} catch (InvalidArgumentException $exception) {
    exit("Кажется, конфигурационный файл отсутствует");
}
// Используем ConfigurationInterface для работы с *.yml форматом
$yamlConfiguration = new Configuration();
// Обрабатываем конфигурационные файлы (объединяем один или больше файлов *.yml)
$processor = new Processor();
$configuration = $processor->processConfiguration(
    $yamlConfiguration,
    array($configuration) // Здесь может быть любое количество *.yml файлов
);

use SymfonyComponentConfigDefinitionConfigurationInterface;
use SymfonyComponentConfigDefinitionBuilderTreeBuilder;
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;
    }
}

Автор: FractalizeR

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js