- PVSM.RU - https://www.pvsm.ru -

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

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

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

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

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

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

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

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

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

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

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

<?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 [5].

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

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

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

к виду

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

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

Финализация

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

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

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

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

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

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

Билдер

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

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

Дампер

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

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

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

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

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

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

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

<?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 [26] вместо ConfigCache для дальнейшей работы. ConfigCacheFactory принимает в конструкторе callable, который будет перестраивать кеш.

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

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

<?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

Источник [28]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/php-2/219954

Ссылки в тексте:

[1] Официальная документация: http://symfony.com/doc/current/components/config/index.html

[2] ArrayNode: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Definition/ArrayNode.php

[3] PrototypedArrayNode: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php

[4] SymfonyComponentConfigDefinitionBuilderTreeBuilder: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Definition/Builder/TreeBuilder.php

[5] NodeBuilder: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php

[6] SymfonyComponentConfigDefinitionNodeInterface: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Definition/NodeInterface.php

[7] SymfonyComponentConfigDefinitionBaseNode: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Definition/BaseNode.php

[8] NodeDefinition: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php

[9] SymfonyComponentConfigDefinitionBuilderValidationBuilder: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Definition/Builder/ValidationBuilder.php

[10] SymfonyComponentConfigDefinitionBuilderMergeBuilder: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Definition/Builder/MergeBuilder.php

[11] NodeDefinition: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php

[12] NodeBuilder: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php

[13] тут: http://symfony.com/doc/current/components/config/definition.html

[14] SymfonyComponentConfigDefinitionDumper: https://github.com/symfony/symfony/tree/2.8/src/Symfony/Component/Config/Definition/Dumper

[15] YamlReferenceDumper: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Definition/Dumper/YamlReferenceDumper.php

[16] XmlReferenceDumper: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php

[17] SymfonyBundleFrameworkBundleCommandConfigDumpReferenceCommand: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Bundle/FrameworkBundle/Command/ConfigDumpReferenceCommand.php

[18] SymfonyComponentConfigResourceResourceInterface: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Resource/ResourceInterface.php

[19] SymfonyComponentConfigResourceCheckerInterface: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/ResourceCheckerInterface.php

[20] SymfonyComponentConfigLoaderLoaderInterface: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Loader/LoaderInterface.php

[21] SymfonyComponentConfigLoaderLoaderResolverInterface: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Loader/LoaderResolverInterface.php

[22] SymfonyComponentConfigLoaderDelegatingLoader: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/Loader/DelegatingLoader.php

[23] SymfonyComponentConfigFileLocator: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/FileLocator.php

[24] SymfonyComponentDependencyInjectionLoaderYamlFileLoader: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

[25] SymfonyComponentConfigConfigCache: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/ConfigCache.php

[26] SymfonyComponentConfigConfigCacheFactory: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Config/ConfigCacheFactory.php

[27] magickatt: https://gist.github.com/magickatt/bb7108d276bce430cf35#file-gistfile1-php

[28] Источник: https://habrahabr.ru/post/271417/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best