如何為 Bundle 建立友善的設定
如果您開啟您的主要應用程式設定目錄 (通常是 config/packages/
),您會看到一些不同的檔案,例如 framework.yaml
、twig.yaml
和 doctrine.yaml
。這些檔案各自設定特定的 bundle,讓您可以定義高階選項,然後讓 bundle 根據您的設定進行所有低階、複雜的變更。
例如,以下設定告訴 FrameworkBundle 啟用表單整合,這牽涉到相當多服務的定義以及其他相關元件的整合
1 2 3
# config/packages/framework.yaml
framework:
form: true
有兩種不同的方法可以為 bundle 建立友善的設定
- 使用主要的 bundle 類別:建議用於新的 bundle 以及遵循建議目錄結構的 bundle;
- 使用 Bundle extension 類別:這是傳統的做法,但現在只建議用於遵循舊版目錄結構的 bundle。
使用 AbstractBundle 類別
在擴展 AbstractBundle 類別的 bundle 中,您可以將所有與處理設定相關的邏輯添加到該類別中
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 36
// 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 $container, ContainerBuilder $builder): void
{
// the "$config" variable is already merged and processed so you can
// use it directly to configure the service container (when defining an
// extension class, you also have to do this merging and processing)
$container->services()
->get('acme_social.twitter_client')
->arg(0, $config['twitter']['client_id'])
->arg(1, $config['twitter']['client_secret'])
;
}
}
注意
configure()
和 loadExtension()
方法僅在編譯時呼叫。
提示
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');
// you can also use glob patterns
//$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()
;
};
使用 Bundle Extension
這是為 bundle 建立友善設定的傳統方法。對於新的 bundle,建議使用主要的 bundle 類別,但建立 extension 類別的傳統方法仍然有效。
假設您正在建立一個新的 bundle - AcmeSocialBundle - 它提供與 X/Twitter 的整合。為了讓您的 bundle 可由使用者設定,您可以新增一些如下所示的設定
1 2 3 4 5
# config/packages/acme_social.yaml
acme_social:
twitter:
client_id: 123
client_secret: your_secret
基本概念是,不要讓使用者覆寫個別參數,而是讓使用者僅設定少數幾個特別建立的選項。作為 bundle 開發人員,然後您會解析該設定,並在 "Extension" 類別中載入正確的服務和參數。
注意
您的 bundle 設定的根金鑰 (acme_social
在先前的範例中) 會自動從您的 bundle 名稱中決定 (它是 bundle 名稱的 蛇形命名法,不包含 Bundle
後綴)。
另請參閱
閱讀更多關於 extension 的資訊,請參閱如何在 Bundle 內載入服務設定。
提示
如果 bundle 提供 Extension 類別,那麼您通常不應從該 bundle 覆寫任何服務容器參數。其概念是,如果存在 extension 類別,則每個應該可設定的設定都應該存在於該類別提供的設定中。換句話說,extension 類別定義了所有將會維護向後相容性的公開設定設定。
另請參閱
關於依賴注入容器內的參數處理,請參閱在依賴注入類別中使用參數。
處理 $configs
陣列
首先,您必須建立一個 extension 類別,如如何在 Bundle 內載入服務設定中所述。
每當使用者在設定檔中包含 acme_social
金鑰 (這是 DI 別名) 時,其下的設定會新增到設定陣列中,並傳遞到您 extension 的 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
[
// values from config/packages/acme_social.yaml
[
'twitter' => [
'client_id' => 123,
'client_secret' => 'your_secret',
],
],
// values from config/packages/dev/acme_social.yaml
[
'twitter' => [
'client_id' => 456,
],
],
]
兩個陣列的順序取決於哪個先設定。
但別擔心!Symfony 的 Config 元件將協助您合併這些值、提供預設值,並在設定錯誤時向使用者提供驗證錯誤。以下是其運作方式。在 DependencyInjection
目錄中建立一個 Configuration
類別,並建立一個樹狀結構,定義您的 bundle 設定的結構。
用於處理範例設定的 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;
}
}
另請參閱
Configuration
類別可能比這裡顯示的更複雜,支援 "原型" 節點、進階驗證、XML 特定的正規化和進階合併。您可以在Config 元件文件中閱讀更多相關資訊。您也可以透過查看一些核心 Configuration 類別來了解其運作方式,例如來自FrameworkBundle Configuration 或 TwigBundle Configuration 的類別。
這個類別現在可以在您的 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);
// you now have these 2 config keys
// $config['twitter']['client_id'] and $config['twitter']['client_secret']
}
processConfiguration()
方法使用您在 Configuration
類別中定義的設定樹狀結構,來驗證、正規化和合併所有設定陣列。
現在,您可以使用 $config
變數來修改您的 bundle 提供的服務。例如,假設您的 bundle 具有以下範例設定
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="https://symfony.dev.org.tw/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://symfony.dev.org.tw/schema/dic/services
https://symfony.dev.org.tw/schema/dic/services/services-1.0.xsd"
>
<services>
<service id="acme_social.twitter_client" class="Acme\SocialBundle\TwitterClient">
<argument></argument> <!-- will be filled in with client_id dynamically -->
<argument></argument> <!-- will be filled in with client_secret dynamically -->
</service>
</services>
</container>
在您的 extension 中,您可以載入此設定並動態設定其引數
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']);
}
提示
您可以選擇使用 ConfigurableExtension 來自動為您執行此操作,而不是在每次提供一些設定選項時都在您的 extension 中呼叫 processConfiguration()
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
{
// note that this method is called loadInternal and not load
protected function loadInternal(array $mergedConfig, ContainerBuilder $container): void
{
// ...
}
}
此類別使用 getConfiguration()
方法來取得 Configuration 實例。
修改另一個 Bundle 的設定
如果您有多個彼此依賴的 bundle,則允許一個 Extension
類別修改傳遞到另一個 bundle 的 Extension
類別的設定可能很有用。這可以使用 prepend extension 來達成。如需更多詳細資訊,請參閱如何簡化多個 Bundle 的設定。
傾印設定
config:dump-reference
命令會使用 Yaml 格式在主控台中傾印 bundle 的預設設定。
只要您的 bundle 設定位於標準位置 (<YourBundle>/src/DependencyInjection/Configuration
) 且沒有建構子,它就會自動運作。如果您有不同的設定,您的 Extension
類別必須覆寫 Extension::getConfiguration() 方法,並傳回您的 Configuration
的實例。
支援 XML
Symfony 允許人們以三種不同的格式提供設定:Yaml、XML 和 PHP。當使用 Config 元件時,Yaml 和 PHP 預設都受到支援。支援 XML 需要您執行更多操作。但是當與其他人分享您的 bundle 時,建議您遵循以下步驟。
讓您的設定樹狀結構準備好支援 XML
Config 元件預設提供一些方法,使其可以正確處理 XML 設定。請參閱元件文件的「定義和處理設定值」。但是,您也可以執行一些可選的操作,這將改善使用 XML 設定的體驗
選擇 XML 命名空間
在 XML 中,XML 命名空間用於判斷哪些元素屬於特定 bundle 的設定。命名空間是從 Extension::getNamespace() 方法傳回的。依照慣例,命名空間是一個 URL (它不一定要是有效的 URL,也不需要存在)。預設情況下,bundle 的命名空間是 http://example.org/schema/dic/DI_ALIAS
,其中 DI_ALIAS
是 extension 的 DI 別名。您可能會想要將其變更為更專業的 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 Schema
XML 具有一個非常有用的功能,稱為 XML schema。這讓您可以描述 XML Schema Definition (XSD 檔案) 中所有可能的元素和屬性及其值。此 XSD 檔案由 IDE 用於自動完成,並由 Config 元件用於驗證元素。
為了使用 schema,XML 設定檔必須提供一個 xsi:schemaLocation
屬性,指向特定 XML 命名空間的 XSD 檔案。此位置始終以 XML 命名空間開頭。然後,此 XML 命名空間會被從 Extension::getXsdValidationBasePath() 方法傳回的 XSD 驗證基礎路徑取代。然後,此命名空間後面會跟著從基礎路徑到檔案本身的其餘路徑。
依照慣例,XSD 檔案位於 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
,則 schema 位置將為 https://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="https://symfony.dev.org.tw/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:acme-hello="http://acme_company.com/schema/dic/hello"
xsi:schemaLocation="https://symfony.dev.org.tw/schema/dic/services
https://symfony.dev.org.tw/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>