跳到內容

定義和處理配置值

編輯此頁面

驗證配置值

從各種資源載入配置值後,可以使用 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 鍵僅在 driversqlite 時才有意義)。

使用 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 選項限制為 standardexpeditedpriority

您也可以將列舉值提供給 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()
;

原型可用於新增定義,該定義可能會在目前節點內重複多次。根據上述範例中的原型定義,可以有多個連線陣列 (包含 driverhost 等)。

有時,為了改善應用程式或套件的使用者體驗,您可以允許在使用陣列值是必要的情況下,使用簡單的字串或數值。使用 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_connectiondefault 配置鍵遺失了。原因是 Symfony Config 組件預設將陣列視為清單。

注意

在撰寫本文時,存在一個不一致之處:如果只有一個檔案提供相關配置,則鍵 (即 sf_connectiondefault) 不會遺失。但是,如果有多個檔案提供配置,則鍵會如上所述遺失。

為了維護陣列鍵,請使用 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)。如果為具有鍵 nameconnections 節點定義了子節點,則對應的鍵將遺失。

此方法的引數 (上述範例中的 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_connectiondefault

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*()
(nulltruefalse),defaultValue() 的捷徑
treat*Like()
(nulltruefalse),在值為 * 的情況下提供替換值。

以下範例示範了這些方法的實際應用

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
);

警告

當處理設定樹狀結構時,處理器會假設頂層陣列鍵 (與擴充功能名稱相符) 已經被移除。

這份作品,包含程式碼範例,以 創用CC 姓名標示-相同方式分享 3.0 授權條款 授權。
TOC (目錄)
    版本