定義和處理配置值
驗證配置值
從各種資源載入配置值後,可以使用 Config 組件的「定義」部分驗證這些值及其結構。配置值通常預期會顯示某種層次結構。此外,值應為特定類型、數量受到限制,或是給定值集合中的一個。例如,以下配置 (以 YAML 格式) 顯示了清晰的層次結構,以及應對其應用的一些驗證規則 (例如:「auto_connect
的值必須是布林值」)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
database:
auto_connect: true
default_connection: mysql
connections:
mysql:
host: localhost
driver: mysql
username: user
password: pass
sqlite:
host: localhost
driver: sqlite
memory: true
username: user
password: pass
當載入多個配置文件時,應可以合併和覆寫某些值。其他值不應合併,而應在首次遇到時保持原樣。此外,某些鍵僅在另一個鍵具有特定值時才可用 (在上面的範例配置中:memory
鍵僅在 driver
為 sqlite
時才有意義)。
使用 TreeBuilder 定義配置值的層次結構
可以使用 TreeBuilder 定義所有關於配置值的規則。
應從自訂 Configuration
類別傳回 TreeBuilder 實例,該類別實作了 ConfigurationInterface
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
namespace Acme\DatabaseConfiguration;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class DatabaseConfiguration implements ConfigurationInterface
{
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('database');
// ... add node definitions to the root of the tree
// $treeBuilder->getRootNode()->...
return $treeBuilder;
}
}
將節點定義添加到樹狀結構
可變節點
樹狀結構包含節點定義,這些定義可以語意化的方式佈局。這表示,使用縮排和流暢表示法,可以反映配置值的真實結構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
$rootNode
->children()
->booleanNode('auto_connect')
->defaultTrue()
->end()
->scalarNode('default_connection')
->defaultValue('mysql')
->end()
->stringNode('username')
->defaultValue('root')
->end()
->stringNode('password')
->defaultValue('root')
->end()
->end()
;
7.2
stringNode()
方法在 Symfony 7.2 中引入。
根節點本身是陣列節點,並且具有子節點,例如布林節點 auto_connect
和純量節點 default_connection
。一般而言:在定義節點後,呼叫 end()
會在層次結構中向上移動一步。
節點類型
可以使用適當的節點定義來驗證提供的值的類型。節點類型適用於
- 純量 (包含布林值、字串、整數、浮點數和
null
的泛型類型) - 布林值
- 字串
- 整數
- 浮點數
- 列舉 (類似於純量,但僅允許有限的值集合)
- 陣列
- 變數 (無驗證)
並使用 node($name, $type)
或其相關聯的捷徑 xxxxNode($name)
方法建立。
7.2
Symfony 7.2 中引入了對 string
類型的支援。
數值節點約束
數值節點 (浮點數和整數) 提供兩個額外的約束 - min() 和 max() - 允許驗證值
1 2 3 4 5 6 7 8 9 10 11 12 13
$rootNode
->children()
->integerNode('positive_value')
->min(0)
->end()
->floatNode('big_value')
->max(5E45)
->end()
->integerNode('value_inside_a_range')
->min(-50)->max(50)
->end()
->end()
;
列舉節點
列舉節點提供一個約束,以將給定的輸入與一組值進行比對
1 2 3 4 5 6 7
$rootNode
->children()
->enumNode('delivery')
->values(['standard', 'expedited', 'priority'])
->end()
->end()
;
這會將 delivery
選項限制為 standard
、expedited
或 priority
。
您也可以將列舉值提供給 enumNode()
。讓我們定義一個列舉,描述上述範例的可能狀態
1 2 3 4 5 6
enum Delivery: string
{
case Standard = 'standard';
case Expedited = 'expedited';
case Priority = 'priority';
}
現在可以像這樣編寫配置
1 2 3 4 5 6 7 8 9 10
$rootNode
->children()
->enumNode('delivery')
// You can provide all values of the enum...
->values(Delivery::cases())
// ... or you can pass only some values next to other scalar values
->values([Delivery::Priority, Delivery::Standard, 'other', false])
->end()
->end()
;
陣列節點
可以透過新增陣列節點,將層次結構新增到更深的層級。陣列節點本身可能具有預定義的可變節點集合
1 2 3 4 5 6 7 8 9 10 11 12
$rootNode
->children()
->arrayNode('connection')
->children()
->scalarNode('driver')->end()
->scalarNode('host')->end()
->scalarNode('username')->end()
->scalarNode('password')->end()
->end()
->end()
->end()
;
或者,您可以為陣列節點內的每個節點定義原型
1 2 3 4 5 6 7 8 9 10 11 12 13 14
$rootNode
->children()
->arrayNode('connections')
->arrayPrototype()
->children()
->scalarNode('driver')->end()
->scalarNode('host')->end()
->scalarNode('username')->end()
->scalarNode('password')->end()
->end()
->end()
->end()
->end()
;
原型可用於新增定義,該定義可能會在目前節點內重複多次。根據上述範例中的原型定義,可以有多個連線陣列 (包含 driver
、host
等)。
有時,為了改善應用程式或套件的使用者體驗,您可以允許在使用陣列值是必要的情況下,使用簡單的字串或數值。使用 castToArray()
輔助程式將這些變數轉換為陣列
1 2 3 4
->arrayNode('hosts')
->beforeNormalization()->castToArray()->end()
// ...
->end()
陣列節點選項
在定義陣列節點的子節點之前,您可以提供如下選項
useAttributeAsKey()
- 提供子節點的名稱,其值應用作結果陣列中的鍵。此方法也定義了配置陣列鍵的處理方式,如下列範例中所述。
requiresAtLeastOneElement()
- 陣列中應至少有一個元素 (僅在也呼叫
isRequired()
時才有效)。 addDefaultsIfNotSet()
- 如果任何子節點具有預設值,請在使用未提供明確值的情況下使用它們。
normalizeKeys(false)
- 如果呼叫 (使用
false
),則帶有破折號的鍵不會正規化為底線。建議將此與使用者將定義鍵值對應的原型節點一起使用,以避免不必要的轉換。 ignoreExtraKeys()
- 允許在陣列下指定額外的配置鍵,而不會擲回例外狀況。
基本的原型陣列配置可以定義如下
1 2 3 4 5 6 7 8
$node
->fixXmlConfig('driver')
->children()
->arrayNode('drivers')
->scalarPrototype()->end()
->end()
->end()
;
當使用以下 YAML 配置時
1
drivers: ['mysql', 'sqlite']
或以下 XML 配置時
1 2
<driver>mysql</driver>
<driver>sqlite</driver>
處理後的配置為
1 2 3 4
Array(
[0] => 'mysql'
[1] => 'sqlite'
)
更複雜的範例是定義具有子節點的原型陣列
1 2 3 4 5 6 7 8 9 10 11 12 13 14
$node
->fixXmlConfig('connection')
->children()
->arrayNode('connections')
->arrayPrototype()
->children()
->scalarNode('table')->end()
->scalarNode('user')->end()
->scalarNode('password')->end()
->end()
->end()
->end()
->end()
;
當使用以下 YAML 配置時
1 2 3
connections:
- { table: symfony, user: root, password: ~ }
- { table: foo, user: root, password: pa$$ }
或以下 XML 配置時
1 2
<connection table="symfony" user="root" password="null"/>
<connection table="foo" user="root" password="pa$$"/>
處理後的配置為
1 2 3 4 5 6 7 8 9 10 11 12
Array(
[0] => Array(
[table] => 'symfony'
[user] => 'root'
[password] => null
)
[1] => Array(
[table] => 'foo'
[user] => 'root'
[password] => 'pa$$'
)
)
先前的輸出符合預期的結果。但是,給定配置樹狀結構,當使用以下 YAML 配置時
1 2 3 4 5 6 7 8 9
connections:
sf_connection:
table: symfony
user: root
password: ~
default:
table: foo
user: root
password: pa$$
輸出配置將與之前完全相同。換句話說,sf_connection
和 default
配置鍵遺失了。原因是 Symfony Config 組件預設將陣列視為清單。
注意
在撰寫本文時,存在一個不一致之處:如果只有一個檔案提供相關配置,則鍵 (即 sf_connection
和 default
) 不會遺失。但是,如果有多個檔案提供配置,則鍵會如上所述遺失。
為了維護陣列鍵,請使用 useAttributeAsKey()
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
$node
->fixXmlConfig('connection')
->children()
->arrayNode('connections')
->useAttributeAsKey('name')
->arrayPrototype()
->children()
->scalarNode('table')->end()
->scalarNode('user')->end()
->scalarNode('password')->end()
->end()
->end()
->end()
->end()
;
注意
在 YAML 中,useAttributeAsKey()
的 'name'
引數具有特殊含義,並且指的是對應的鍵 (sf_connection
和此範例中的 default
)。如果為具有鍵 name
的 connections
節點定義了子節點,則對應的鍵將遺失。
此方法的引數 (上述範例中的 name
) 定義了新增至每個 XML 節點以區分它們的屬性名稱。現在您可以使用之前顯示的相同 YAML 配置或以下 XML 配置
1 2 3 4
<connection name="sf_connection"
table="symfony" user="root" password="null"/>
<connection name="default"
table="foo" user="root" password="pa$$"/>
在這兩種情況下,處理後的配置都維護了 sf_connection
和 default
鍵
1 2 3 4 5 6 7 8 9 10 11 12
Array(
[sf_connection] => Array(
[table] => 'symfony'
[user] => 'root'
[password] => null
)
[default] => Array(
[table] => 'foo'
[user] => 'root'
[password] => 'pa$$'
)
)
預設值和必要值
對於所有節點類型,都可以定義預設值和替換值,以防節點具有特定值
defaultValue()
- 設定預設值
isRequired()
- 必須定義 (但可能為空)
cannotBeEmpty()
- 不得包含空值
default*()
- (
null
、true
、false
),defaultValue()
的捷徑 treat*Like()
- (
null
、true
、false
),在值為*
的情況下提供替換值。
以下範例示範了這些方法的實際應用
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
$rootNode
->children()
->arrayNode('connection')
->children()
->scalarNode('driver')
->isRequired()
->cannotBeEmpty()
->end()
->scalarNode('host')
->defaultValue('localhost')
->end()
->scalarNode('username')->end()
->scalarNode('password')->end()
->booleanNode('memory')
->defaultFalse()
->end()
->end()
->end()
->arrayNode('settings')
->addDefaultsIfNotSet()
->children()
->scalarNode('name')
->isRequired()
->cannotBeEmpty()
->defaultValue('value')
->end()
->end()
->end()
->end()
;
棄用選項
您可以使用 setDeprecated() 方法棄用選項
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
$rootNode
->children()
->integerNode('old_option')
// this outputs the following generic deprecation message:
// Since acme/package 1.2: The child node "old_option" at path "..." is deprecated.
->setDeprecated('acme/package', '1.2')
// you can also pass a custom deprecation message (%node% and %path% placeholders are available):
->setDeprecated(
'acme/package',
'1.2',
'The "%node%" option is deprecated. Use "new_config_option" instead.'
)
->end()
->end()
;
如果您使用 Web Debug Toolbar,則在重建配置時會顯示這些棄用通知。
記錄選項
可以使用 info() 方法記錄所有選項
1 2 3 4 5 6 7 8
$rootNode
->children()
->integerNode('entries_per_page')
->info('This value is only used for the search results page.')
->defaultValue(25)
->end()
->end()
;
使用 config:dump-reference
命令傾印配置樹狀結構時,資訊將列印為註解。
在 YAML 中,您可以有
1 2
# This value is only used for the search results page.
entries_per_page: 25
在 XML 中,您可以有
1 2
<!-- entries-per-page: This value is only used for the search results page. -->
<config entries-per-page="25"/>
可選章節
如果您有整個章節是可選的,並且可以啟用/停用,則可以利用捷徑 canBeEnabled() 和 canBeDisabled() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
$arrayNode
->canBeEnabled()
;
// is equivalent to
$arrayNode
->treatFalseLike(['enabled' => false])
->treatTrueLike(['enabled' => true])
->treatNullLike(['enabled' => true])
->children()
->booleanNode('enabled')
->defaultFalse()
;
canBeDisabled()
方法看起來大致相同,只是該章節預設為啟用。
合併選項
可以提供關於合併程序的額外選項。對於陣列
performNoDeepMerging()
- 當值也在第二個配置陣列中定義時,請勿嘗試合併陣列,而是完全覆寫它
對於所有節點
cannotBeOverwritten()
- 不要讓其他配置陣列覆寫此節點的現有值
附加章節
如果您有複雜的配置要驗證,則樹狀結構可能會變得很大,而您可能想要將其分成幾個部分。您可以透過將章節設為單獨的節點,然後使用 append()
將其附加到主樹狀結構中來完成此操作
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 37 38 39 40 41 42 43 44 45 46 47 48
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('database');
$treeBuilder->getRootNode()
->children()
->arrayNode('connection')
->children()
->scalarNode('driver')
->isRequired()
->cannotBeEmpty()
->end()
->scalarNode('host')
->defaultValue('localhost')
->end()
->scalarNode('username')->end()
->scalarNode('password')->end()
->booleanNode('memory')
->defaultFalse()
->end()
->end()
->append($this->addParametersNode())
->end()
->end()
;
return $treeBuilder;
}
public function addParametersNode(): NodeDefinition
{
$treeBuilder = new TreeBuilder('parameters');
$node = $treeBuilder->getRootNode()
->isRequired()
->requiresAtLeastOneElement()
->useAttributeAsKey('name')
->arrayPrototype()
->children()
->scalarNode('value')->isRequired()->end()
->end()
->end()
;
return $node;
}
如果您有在不同位置重複的配置章節,這也可用於協助您避免重複自己。
範例產生以下結果
1 2 3 4 5 6 7 8 9 10 11 12
database:
connection:
driver: ~ # Required
host: localhost
username: ~
password: ~
memory: false
parameters: # Required
# Prototype
name:
value: ~ # Required
正規化
當處理配置檔案時,它們會先正規化,然後合併,最後使用樹狀結構來驗證產生的陣列。正規化程序用於消除不同配置格式產生的一些差異,主要是 YAML 和 XML 之間的差異。
在 YAML 中,鍵中使用的分隔符通常為 _
,而在 XML 中則為 -
。例如,YAML 中的 auto_connect
和 XML 中的 auto-connect
。正規化會將這兩者都設為 auto_connect
。
警告
如果目標鍵是混合的 (例如 foo-bar_moo
) 或已存在,則不會變更目標鍵。
YAML 和 XML 之間的另一個差異是表示值陣列的方式。在 YAML 中,您可以有
1 2
twig:
extensions: ['twig.extension.foo', 'twig.extension.bar']
在 XML 中,您可以有
1 2 3 4
<twig:config>
<twig:extension>twig.extension.foo</twig:extension>
<twig:extension>twig.extension.bar</twig:extension>
</twig:config>
可以透過將 XML 中使用的鍵複數化,在正規化中消除此差異。您可以指定您想要以這種方式複數化鍵,方法是使用 fixXmlConfig()
1 2 3 4 5 6 7 8
$rootNode
->fixXmlConfig('extension')
->children()
->arrayNode('extensions')
->scalarPrototype()->end()
->end()
->end()
;
如果是詞形不規則的複數化,您可以指定要用作第二個引數的複數
1 2 3 4 5 6 7 8
$rootNode
->fixXmlConfig('child', 'children')
->children()
->arrayNode('children')
// ...
->end()
->end()
;
除了修正此問題之外,fixXmlConfig()
還確保單個 XML 元素仍然轉換為陣列。因此,您可能有
1 2
<connection>default</connection>
<connection>extra</connection>
有時只有
1
<connection>default</connection>
預設情況下,connection
在第一種情況下將是陣列,而在第二種情況下將是字串,這使得驗證變得困難。您可以使用 fixXmlConfig()
確保它始終是陣列。
如果需要,您可以進一步控制正規化程序。例如,您可能想要允許設定字串並將其用作特定鍵,或者明確設定多個鍵。因此,如果此配置中除了 name
之外的所有內容都是可選的
1 2 3 4 5 6
connection:
name: my_mysql_connection
host: localhost
driver: mysql
username: user
password: pass
您可以允許以下內容
1
connection: my_mysql_connection
透過將字串值變更為以 name
作為鍵的關聯陣列
1 2 3 4 5 6 7 8 9 10 11 12 13 14
$rootNode
->children()
->arrayNode('connection')
->beforeNormalization()
->ifString()
->then(function (string $v): array { return ['name' => $v]; })
->end()
->children()
->scalarNode('name')->isRequired()->end()
// ...
->end()
->end()
->end()
;
驗證規則
可以使用 ExprBuilder 來提供更進階的驗證規則。此建構器實作了用於常見控制結構的流暢介面。此建構器用於為節點定義添加進階驗證規則,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
$rootNode
->children()
->arrayNode('connection')
->children()
->scalarNode('driver')
->isRequired()
->validate()
->ifNotInArray(['mysql', 'sqlite', 'mssql'])
->thenInvalid('Invalid database driver %s')
->end()
->end()
->end()
->end()
->end()
;
驗證規則總是會有一個 "if" (如果) 部分。您可以用以下方式指定這個部分
ifTrue() (如果為真)
ifString() (如果為字串)
ifNull() (如果為空值)
ifEmpty() (如果為空)
ifArray() (如果為陣列)
ifInArray() (如果在陣列中)
ifNotInArray() (如果不在陣列中)
always() (總是)
驗證規則也需要一個 "then" (然後) 部分
then() (然後)
thenEmptyArray() (然後為空陣列)
thenInvalid() (然後無效)
thenUnset() (然後取消設定)
通常,"then" 是一個閉包 (closure)。它的回傳值將會被用作節點的新值,而不是節點的原始值。
配置節點路徑分隔符
考慮以下設定建構器的範例
1 2 3 4 5 6 7 8 9 10 11
$treeBuilder = new TreeBuilder('database');
$treeBuilder->getRootNode()
->children()
->arrayNode('connection')
->children()
->scalarNode('driver')->end()
->end()
->end()
->end()
;
預設情況下,設定路徑中的節點階層結構是用點字元 (.
) 定義的
1 2 3 4 5 6 7
// ...
$node = $treeBuilder->buildTree();
$children = $node->getChildren();
$childChildren = $children['connection']->getChildren();
$path = $childChildren['driver']->getPath();
// $path = 'database.connection.driver'
在設定建構器上使用 setPathSeparator()
方法來變更路徑分隔符號
1 2 3 4 5 6 7 8
// ...
$treeBuilder->setPathSeparator('/');
$node = $treeBuilder->buildTree();
$children = $node->getChildren();
$childChildren = $children['connection']->getChildren();
$path = $childChildren['driver']->getPath();
// $path = 'database/connection/driver'
處理配置值
Processor 使用以 TreeBuilder 建構的樹狀結構,來處理多個應合併的設定值陣列。如果任何值不是預期的類型、是強制性的但尚未定義,或者無法以其他方式驗證,則會拋出例外。否則,結果會是一個乾淨的設定值陣列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use Acme\DatabaseConfiguration;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Yaml\Yaml;
$config = Yaml::parse(
file_get_contents(__DIR__.'/src/Matthias/config/config.yaml')
);
$extraConfig = Yaml::parse(
file_get_contents(__DIR__.'/src/Matthias/config/config_extra.yaml')
);
$configs = [$config, $extraConfig];
$processor = new Processor();
$databaseConfiguration = new DatabaseConfiguration();
$processedConfiguration = $processor->processConfiguration(
$databaseConfiguration,
$configs
);
警告
當處理設定樹狀結構時,處理器會假設頂層陣列鍵 (與擴充功能名稱相符) 已經被移除。