Як створити дружню конфігурацію для пакета
Дата оновлення перекладу 2024-04-30
Як створити дружню конфігурацію для пакета
Якщо ви відкриєте ваш головний каталог додатку (зазвичай config/packages/
),
то ви побачите деяку кількість різних файлів, на кшталт framework.yaml
,
twig.yaml
та doctrine.yaml
. Кожний з них конфігурує особливий пакет, що
дозволяє вам визначати опції на високому рівні. а потім дозволяє пакету зробити
всі складні зміни нижчого рівня, грунтуючись на ваших налаштуваннях.
Наприклад, наступна конфігурація повідомляє FrameworkBundle підключити інтеграцію форми, яка задіює визначення немаленької кількості сервісів, а також інтеграцію повʼязаних з ними компонентів:
1 2 3
# config/packages/framework.yaml
framework:
form: true
Використання розширення пакета
Уявіть, що ви створюєте новий пакет - AcmeSocialBundle - який надає інтеграцію з Twitter. Щоб зробити ваш пакет конфігурованим для користувача, ви можете додати деяку конфігурацію, яка виглядатиме так:
1 2 3 4 5
# config/packages/acme_social.yaml
acme_social:
twitter:
client_id: 123
client_secret: your_secret
Основна ідея полягає в тому, що замість того, щоб користувач перевизначав окремі параметри, ви можете дозволити користувачу сконфігурувати декілька спеціально створених опцій. Як розробник пакета, ви потім парсуєте цю конфігурацію і завантажуєте правильні сервіси та параметри всередині класу "Extension".
Note
Кореневий ключ конфігурації вашого пакета (acme_social
у попередньому
прикладі) автоматично визначається з імені вашого пакету (це зміїинй регістр
імені пакета без суфіксу Bundle
).
See also
Прочитайте більше про розширення в Як завантажувати конфігурацію сервісу всередині пакета.
Tip
Якщо пакет надає клас розширення, то вам не варто просто перевизначати будь-які параметри сервіс-контейнера з цього пакета. Ідея полягає в тому, що якщо присутній клас розширення, то кожне налаштування, яке має бути конфігурованим, має бути представлена у конфігурації та доступня для цього класу. Іншими словами, клас розширення визначає усі публічні налаштування конфігурації для яких зберігається зворотня сумісність.
See also
Для роботи з параметрами в рамках контейнера впровадження залежності, див. Використання параметрів у класі впровадження залежностей.
Обробка масиву $configs
Перршочергово вам потрібно створити клас розширення так, як пояснюється у Як завантажувати конфігурацію сервісу всередині пакета.
Кожний раз, коли користувач додає ключ acme_social
(який є додатковим іменем
ВЗ) у файлі конфігурації, конфігурація під ним додається в масив конфігурації та
передається методу load()
вашого розширення (Symfony автоматично конвертує
XML та YAML у масив).
Для прикладу конфігурації в попердньому розділі, масив, переданий вашому методу
load()
виглядатиме так:
1 2 3 4 5 6 7 8
[
[
'twitter' => [
'client_id' => 123,
'client_secret' => 'your_secret',
],
],
]
Відмітьте, що це масив масивів, а не просто плаский масив значень конфігурації.
Це зроблено спеціально, так як дозволяє Symfony аналізувати декілька джерел
конфігурації. Наприклад, якщо acme_social
зʼявляється в іншому файлі конфігурації
- скажімо, config/packages/dev/acme_social.yaml
- з іншими значеннями під ним,
вхідний масив може виглядати так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[
// значення з config/packages/acme_social.yaml
[
'twitter' => [
'client_id' => 123,
'client_secret' => 'your_secret',
],
],
// значення з config/packages/dev/acme_social.yaml
[
'twitter' => [
'client_id' => 456,
],
],
]
Порядок двох масивів залежить від того, який встановлено першим.
Але не хвилюйтеся! Компонент Symfony Конфігурація допоможе вам обʼєднати ці значення,
надасть значення за замовчуванням та видасть користувачу помилки валідації у поганій
конфігурації. Ось, як це працює. Створіть клас Configuration
у каталозі
DependencyInjection
та побудуйте дерево, що визначає структуру конфігурації вашого
пакета.
Клас Configuration
для обробки прробної конфігурації виглядає так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
// src/DependencyInjection/Configuration.php
namespace Acme\SocialBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('acme_social');
$treeBuilder->getRootNode()
->children()
->arrayNode('twitter')
->children()
->integerNode('client_id')->end()
->scalarNode('client_secret')->end()
->end()
->end() // twitter
->end()
;
return $treeBuilder;
}
}
See also
Клас Configuration
може бути набагато складнішим, ніж продемонстровано тут,
підтримувати вузли "прототипів", просунуту валідацію, XML нормалізацію та
прросунуте обʼєднання. Ви можете прочитати більше про це у
документації компонента Конфігурація.
Ви також можете побачити це в дії, вивчивши деякі базові класи Конфігурації,
як, наприклад, Конфігурацію FrameworkBundle або Конфігурацію TwigBundle.
Цей клас тепер може бути використаний у вашому методі load()
для злиття конфігурацій
та форсування валідації (наприклад, якщо була передана додаткова опція, буде видано виключення):
1 2 3 4 5 6 7 8 9 10
// src/DependencyInjection/AcmeSocialExtension.php
public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
// тепер у вас є ці 2 ключі конфігурації
// $config['twitter']['client_id'] and $config['twitter']['client_secret']
}
Метод processConfiguration()
використовує дерево конфігурації, яке ви визначили
у класі Configuration
для валідації, нормалізації та злиття всіх масивів конфігурації.
Тепер ви можете використати змінну $config
для зміни сервісу, наданого
вашим пакетом.
Наприклад, уявіть, що ваш пакет має наступний приклад конфігурації:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
<!-- src/config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd"
>
<services>
<service id="acme.social.twitter_client" class="Acme\SocialBundle\TwitterClient">
<argument></argument> <!-- буде динамічно заповнено за допомогою client_id -->
<argument></argument> <!-- буде динамічно заповнено за допомогою client_secret -->
</service>
</services>
</container>
У вашому розширенні, ви можете завантажити це та динамічно встановлювати його аргументи:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/DependencyInjection/AcmeSocialExtension.php
namespace Acme\SocialBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new XmlFileLoader($container, new FileLocator(dirname(__DIR__).'/Resources/config'));
$loader->load('services.xml');
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$definition = $container->getDefinition('acme.social.twitter_client');
$definition->replaceArgument(0, $config['twitter']['client_id']);
$definition->replaceArgument(1, $config['twitter']['client_secret']);
}
Tip
Замість виклику processConfiguration()
у вашому розширенні кожний раз, коли ви
надаєте деякі опції конфігурації, ви можете захотіти використовувати
ConfigurableExtension,
щоб він робив це все за вас автоматично:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// src/DependencyInjection/HelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
class AcmeHelloExtension extends ConfigurableExtension
{
// відмітьте, що цей метод називається loadInternal, а не завантажує захищену
функцію loadInternal(array $mergedConfig, ContainerBuilder $container)
{
// ...
}
}
Цей клас використовує метод getConfiguration()
, щоб отримати екземпляр
Конфігурації.
Використання класу AbstractBundle
Як варіант, замість створення розширення та класу конфігурації як продемонстровано у попередньому розділі, ви також можете розшрити AbstractBundle, щоб додати цю логіку прямо до класу пакету:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
// src/AcmeSocialBundle.php
namespace Acme\SocialBundle;
use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
class AcmeSocialBundle extends AbstractBundle
{
public function configure(DefinitionConfigurator $definition): void
{
$definition->rootNode()
->children()
->arrayNode('twitter')
->children()
->integerNode('client_id')->end()
->scalarNode('client_secret')->end()
->end()
->end() // twitter
->end()
;
}
public function loadExtension(array $config, ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void
{
// На відміну від класу Розширення, змінна "$config" вже злита та оброблена.
// Ви можете використати її напряму, щоб сконфігурувати сервіс-контейнер.
$containerConfigurator->services()
->get('acme.social.twitter_client')
->arg(0, $config['twitter']['client_id'])
->arg(1, $config['twitter']['client_secret'])
;
}
}
Note
Методи configure()
та loadExtension()
викликаються тільки під час компіляції.
Tip
Метод AbstractBundle::configure()
також дозволяє імпортувати визначення конфігурації
з одного або більше файлів:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// src/AcmeSocialBundle.php
namespace Acme\SocialBundle;
// ...
class AcmeSocialBundle extends AbstractBundle
{
public function configure(DefinitionConfigurator $definition): void
{
$definition->import('../config/definition.php');
// ви можете також використати глобальні патерни
//$definition->import('../config/definition/*.php');
}
// ...
}
1 2 3 4 5 6 7 8 9 10
// config/definition.php
use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
return static function (DefinitionConfigurator $definition): void {
$definition->rootNode()
->children()
->scalarNode('foo')->defaultValue('bar')->end()
->end()
;
};
Зміна конфігурації іншого пакета
Якщо у вас є декілька пакетів, що залежать одне від одного, може бути корисним
дозволити одному класу Extension
змінювати конфігурацію, передану класу
Extension
іншого пакета. Цього можна досягти, використовуючи розширення додатку.
Щоб дізнатися більше, див. Як спростити конфігурацію декількох пакетів.
Скидання конфігурації
Команда config:dump-reference
скидає конфігурацію пакета за замовчуванням
у консолі, використовуючи формат Yaml.
Якщо конфігурація вашого пакета знаходиться у стандартній локації
(YourBundle\DependencyInjection\Configuration
) та немає конструктора,
то вона буде працювати автоматично. Якщо ж у вас щось по-іншому, ваш клас
Extension
має перевизначати метод
Extension::getConfiguration()
та повертати екземпляр вашої Configuration
.
Підтримка XML
Symfony дозволяє людям надавати конфігурацію у трьох різних форматах: Yaml, XML і PHP. Як Yaml, так і PHP використовують однаковий синтаксис та пітдримуються за замовчуванням при використанні компонента Конфігурація. Підтримка XML вимагає від вас деяких речей. Але при загальному використанні пакета з іншими, рекомендується слідувати цим крокам.
Підготуйте ваше дерево конфігурації до XML
Компонент Конфігурація надає деякі методи за замовчуванням, щоб дозволяти йому коректно обробляти XML-конфігурацію. Дивіться "" у документації компонента. Однак, ви можете зробити деякі додаткові речі, які покращать досвід використання XML-конфігурації:
Вибір простору імен XML
В XML, простір імен XML використовується для визначення того, які елементи
належать конфігурації конкретного пакета. Простір імен повертається з методу
Extension::getNamespace().
За домовленістю, простір імен - це URL (він не має бути валідним або в принципі існувати).
За замовчуванням, простір імен для пакета - http://example.org/schema/dic/DI_ALIAS
,
де DI_ALIAS
- додаткове імʼя впровадження розширення. Ви можете захотіти
змінити це на більш професійний URL:
1 2 3 4 5 6 7 8 9 10 11 12 13
// src/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
// ...
class AcmeHelloExtension extends Extension
{
// ...
public function getNamespace(): string
{
return 'http://acme_company.com/schema/dic/hello';
}
}
Надання XML-схеми
XML має дуже корисну функцію, під назвою XML-схема. Вона дозволяє вам описати всі можливі елементи та атрибути, а також їх значення, у визначенні XML-схеми (xsd-файл). Цей XSD-файл використовується інтегрованим середовищем обробки для автозаповнення та використовується компонентом Конфігурація для валідації елементів.
Для того, шоб використати схему, файл XML-конфігурації повинен надавати атрибут
xsi:schemaLocation
, який вказує на XSD-файл для певного простору імен XML. Це
місцезнаходження завжди починається з простору імен XML. Цей простір імен XML потім
замінюється базовим шляхом XSD-валідації, що повертаються з методу
Extension::getXsdValidationBasePath().
Потім за цим простором імен іде решта шляху з базового шляху до самого файлу.
За домовленістю, XSD-файл живе у Resources/config/schema/
, але ви можете
розмістити його де завгодно. Вам варто повернути цей шлях в якості базового:
1 2 3 4 5 6 7 8 9 10 11 12 13
// src/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
// ...
class AcmeHelloExtension extends Extension
{
// ...
public function getXsdValidationBasePath(): string
{
return __DIR__.'/../config/schema';
}
}
Якщо припустити, що XSD-файл називається hello-1.0.xsd
, то місцезнаходження
схеми буде http://acme_company.com/schema/dic/hello/hello-1.0.xsd
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<!-- config/packages/acme_hello.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:acme-hello="http://acme_company.com/schema/dic/hello"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://acme_company.com/schema/dic/hello
https://acme_company.com/schema/dic/hello/hello-1.0.xsd"
>
<acme-hello:config>
<!-- ... -->
</acme-hello:config>
<!-- ... -->
</container>