The OptionsResolver Component

The OptionsResolver component is an improved replacement for the array_replace PHP function. It allows you to create an options system with required options, defaults, validation (type, value), normalization and more.

OptionsResolver コンポーネントは、array_replace PHP 関数の改良版です。必要なオプション、デフォルト、検証 (タイプ、値)、正規化などを含むオプション システムを作成できます。

Installation

1
$ composer require symfony/options-resolver

Note

ノート

If you install this component outside of a Symfony application, you must require the vendor/autoload.php file in your code to enable the class autoloading mechanism provided by Composer. Read this article for more details.

このコンポーネントを Symfony アプリケーションの外部にインストールする場合は、Composer が提供するクラス自動ロード メカニズムを有効にするために、コード内に vendor/autoload.php ファイルを必要とする必要があります。詳細については、この記事をお読みください。

Usage

Imagine you have a Mailer class which has four options: host, username, password and port:

ホスト、ユーザー名、パスワード、ポートの 4 つのオプションを持つ Mailer クラスがあるとします。
1
2
3
4
5
6
7
8
9
class Mailer
{
    protected $options;

    public function __construct(array $options = [])
    {
        $this->options = $options;
    }
}

When accessing the $options, you need to add some boilerplate code to check which options are set:

$options にアクセスするときは、どのオプションが設定されているかを確認する定型コードを追加する必要があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Mailer
{
    // ...
    public function sendMail($from, $to)
    {
        $mail = ...;

        $mail->setHost($this->options['host'] ?? 'smtp.example.org');
        $mail->setUsername($this->options['username'] ?? 'user');
        $mail->setPassword($this->options['password'] ?? 'pa$$word');
        $mail->setPort($this->options['port'] ?? 25);

        // ...
    }
}

Also, the default values of the options are buried in the business logic of your code. Use the array_replace to fix that:

また、オプションのデフォルト値は、コードのビジネス ロジックに埋め込まれています。 array_replace を使用して修正します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Mailer
{
    // ...

    public function __construct(array $options = [])
    {
        $this->options = array_replace([
            'host'     => 'smtp.example.org',
            'username' => 'user',
            'password' => 'pa$$word',
            'port'     => 25,
        ], $options);
    }
}

Now all four options are guaranteed to be set, but you could still make an error like the following when using the Mailer class:

これで、4 つのオプションすべてが設定されることが保証されますが、Mailer クラスを使用すると、次のようなエラーが発生する可能性があります。
1
2
3
$mailer = new Mailer([
    'usernme' => 'johndoe',  // 'username' is wrongly spelled as 'usernme'
]);

No error will be shown. In the best case, the bug will appear during testing, but the developer will spend time looking for the problem. In the worst case, the bug might not appear until it's deployed to the live system.

エラーは表示されません。最良の場合、バグはテスト中に表示されますが、開発者は問題を探すのに時間を費やします。最悪の場合、ライブ システムにデプロイされるまでバグが表示されない可能性があります。

Fortunately, the OptionsResolver class helps you to fix this problem:

幸いなことに、OptionsResolver クラスは、この問題を解決するのに役立ちます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use Symfony\Component\OptionsResolver\OptionsResolver;

class Mailer
{
    // ...

    public function __construct(array $options = [])
    {
        $resolver = new OptionsResolver();
        $resolver->setDefaults([
            'host'     => 'smtp.example.org',
            'username' => 'user',
            'password' => 'pa$$word',
            'port'     => 25,
        ]);

        $this->options = $resolver->resolve($options);
    }
}

Like before, all options will be guaranteed to be set. Additionally, an UndefinedOptionsException is thrown if an unknown option is passed:

以前と同様に、すべてのオプションが設定されることが保証されます。さらに、不明なオプションが渡されると、UndefinedOptionsException がスローされます。
1
2
3
4
5
6
$mailer = new Mailer([
    'usernme' => 'johndoe',
]);

// UndefinedOptionsException: The option "usernme" does not exist.
// Defined options are: "host", "password", "port", "username"

The rest of your code can access the values of the options without boilerplate code:

コードの残りの部分は、定型コードなしでオプションの値にアクセスできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ...
class Mailer
{
    // ...

    public function sendMail($from, $to)
    {
        $mail = ...;
        $mail->setHost($this->options['host']);
        $mail->setUsername($this->options['username']);
        $mail->setPassword($this->options['password']);
        $mail->setPort($this->options['port']);
        // ...
    }
}

It's a good practice to split the option configuration into a separate method:

オプション構成を別の方法に分割することをお勧めします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ...
class Mailer
{
    // ...

    public function __construct(array $options = [])
    {
        $resolver = new OptionsResolver();
        $this->configureOptions($resolver);

        $this->options = $resolver->resolve($options);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'host'       => 'smtp.example.org',
            'username'   => 'user',
            'password'   => 'pa$$word',
            'port'       => 25,
            'encryption' => null,
        ]);
    }
}

First, your code becomes easier to read, especially if the constructor does more than processing options. Second, sub-classes may now override the configureOptions() method to adjust the configuration of the options:

まず、特にコンストラクターがオプションの処理以上のことを行う場合、コードが読みやすくなります。次に、サブクラスが configureOptions() メソッドをオーバーライドして、オプションの構成を調整できるようになりました。
1
2
3
4
5
6
7
8
9
10
11
12
13
// ...
class GoogleMailer extends Mailer
{
    public function configureOptions(OptionsResolver $resolver)
    {
        parent::configureOptions($resolver);

        $resolver->setDefaults([
            'host' => 'smtp.google.com',
            'encryption' => 'ssl',
        ]);
    }
}

Required Options

If an option must be set by the caller, pass that option to setRequired(). For example, to make the host option required, you can do:

呼び出し元がオプションを設定する必要がある場合は、そのオプションを setRequired() に渡します。たとえば、ホスト オプションを必須にするには、次のようにします。
1
2
3
4
5
6
7
8
9
10
11
// ...
class Mailer
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        // ...
        $resolver->setRequired('host');
    }
}

If you omit a required option, a MissingOptionsException will be thrown:

必要なオプションを省略すると、MissingOptionsException がスローされます。
1
2
3
$mailer = new Mailer();

// MissingOptionsException: The required option "host" is missing.

The setRequired() method accepts a single name or an array of option names if you have more than one required option:

setRequired() メソッドは、複数の必須オプションがある場合、単一の名前またはオプション名の配列を受け入れます。
1
2
3
4
5
6
7
8
9
10
11
// ...
class Mailer
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        // ...
        $resolver->setRequired(['host', 'username', 'password']);
    }
}

Use isRequired() to find out if an option is required. You can use getRequiredOptions() to retrieve the names of all required options:

オプションが必要かどうかを調べるには isRequired() を使用します。 getRequiredOptions() を使用して、必要なすべてのオプションの名前を取得できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ...
class GoogleMailer extends Mailer
{
    public function configureOptions(OptionsResolver $resolver)
    {
        parent::configureOptions($resolver);

        if ($resolver->isRequired('host')) {
            // ...
        }

        $requiredOptions = $resolver->getRequiredOptions();
    }
}

If you want to check whether a required option is still missing from the default options, you can use isMissing(). The difference between this and isRequired() is that this method will return false if a required option has already been set:

必要なオプションがまだデフォルトのオプションにないかどうかを確認したい場合は、isMissing() を使用できます。これと isRequired() の違いは、必要なオプションが既に設定されている場合、このメソッドは 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
31
32
33
34
// ...
class Mailer
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        // ...
        $resolver->setRequired('host');
    }
}

// ...
class GoogleMailer extends Mailer
{
    public function configureOptions(OptionsResolver $resolver)
    {
        parent::configureOptions($resolver);

        $resolver->isRequired('host');
        // => true

        $resolver->isMissing('host');
        // => true

        $resolver->setDefault('host', 'smtp.google.com');

        $resolver->isRequired('host');
        // => true

        $resolver->isMissing('host');
        // => false
    }
}

The getMissingOptions() method lets you access the names of all missing options.

getMissingOptions() メソッドを使用すると、不足しているすべてのオプションの名前にアクセスできます。

Type Validation

You can run additional checks on the options to make sure they were passed correctly. To validate the types of the options, call setAllowedTypes():

オプションに対して追加のチェックを実行して、それらが正しく渡されたことを確認できます。オプションのタイプを検証するには、setAllowedTypes() を呼び出します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ...
class Mailer
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        // ...

        // specify one allowed type
        $resolver->setAllowedTypes('host', 'string');

        // specify multiple allowed types
        $resolver->setAllowedTypes('port', ['null', 'int']);

        // check all items in an array recursively for a type
        $resolver->setAllowedTypes('dates', 'DateTime[]');
        $resolver->setAllowedTypes('ports', 'int[]');
    }
}

You can pass any type for which an is_<type>() function is defined in PHP. You may also pass fully qualified class or interface names (which is checked using instanceof). Additionally, you can validate all items in an array recursively by suffixing the type with [].

PHP で is_() 関数が定義されている任意の型を渡すことができます。また、完全修飾されたクラスまたはインターフェイス名 (instanceof を使用してチェックされます) を渡すこともできます。さらに、型の末尾に [] を付けることで、配列内のすべての項目を再帰的に検証できます。

If you pass an invalid option now, an InvalidOptionsException is thrown:

ここで無効なオプションを渡すと、InvalidOptionsException がスローされます。
1
2
3
4
5
6
$mailer = new Mailer([
    'host' => 25,
]);

// InvalidOptionsException: The option "host" with value "25" is
// expected to be of type "string", but is of type "int"

In sub-classes, you can use addAllowedTypes() to add additional allowed types without erasing the ones already set.

サブクラスでは、 addAllowedTypes() を使用して、既に設定されているタイプを消去せずに、許可されたタイプを追加できます。

Value Validation

Some options can only take one of a fixed list of predefined values. For example, suppose the Mailer class has a transport option which can be one of sendmail, mail and smtp. Use the method setAllowedValues() to verify that the passed option contains one of these values:

一部のオプションは、事前定義された値の固定リストの 1 つしか取ることができません。たとえば、Mailer クラスに、sendmail、mail、および smtp のいずれかのトランスポート オプションがあるとします。 methodsetAllowedValues() を使用して、渡されたオプションに次のいずれかの値が含まれていることを確認します。
1
2
3
4
5
6
7
8
9
10
11
12
// ...
class Mailer
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        // ...
        $resolver->setDefault('transport', 'sendmail');
        $resolver->setAllowedValues('transport', ['sendmail', 'mail', 'smtp']);
    }
}

If you pass an invalid transport, an InvalidOptionsException is thrown:

無効なトランスポートを渡すと、InvalidOptionsException がスローされます。
1
2
3
4
5
6
$mailer = new Mailer([
    'transport' => 'send-mail',
]);

// InvalidOptionsException: The option "transport" with value "send-mail"
// is invalid. Accepted values are: "sendmail", "mail", "smtp"

For options with more complicated validation schemes, pass a closure which returns true for acceptable values and false for invalid values:

より複雑な検証スキームを持つオプションの場合、許容値に対して true を返し、無効な値に対して false を返すクロージャーを渡します。
1
2
3
4
// ...
$resolver->setAllowedValues('transport', function ($value) {
    // return true or false
});

Tip

ヒント

You can even use the Validator component to validate the input by using the createIsValidCallable() method:

createIsValidCallable() メソッドを使用して Validator コンポーネントを使用して入力を検証することもできます。
1
2
3
4
5
6
7
8
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Validation;

// ...
$resolver->setAllowedValues('transport', Validation::createIsValidCallable(
    new Length(['min' => 10 ])
));

In sub-classes, you can use addAllowedValues() to add additional allowed values without erasing the ones already set.

サブクラスでは、 addAllowedValues() を使用して、既に設定されている値を消去することなく、許可された値を追加できます。

Option Normalization

Sometimes, option values need to be normalized before you can use them. For instance, assume that the host should always start with http://. To do that, you can write normalizers. Normalizers are executed after validating an option. You can configure a normalizer by calling setNormalizer():

場合によっては、オプション値を使用する前に正規化する必要があります。たとえば、ホストは常に http:// で始まる必要があるとします。そのために、ノーマライザーを作成できます。ノーマライザーは、オプションの検証後に実行されます。 setNormalizer() を呼び出すことでノーマライザーを構成できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use Symfony\Component\OptionsResolver\Options;

// ...
class Mailer
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        // ...

        $resolver->setNormalizer('host', function (Options $options, $value) {
            if ('http://' !== substr($value, 0, 7)) {
                $value = 'http://'.$value;
            }

            return $value;
        });
    }
}

The normalizer receives the actual $value and returns the normalized form. You see that the closure also takes an $options parameter. This is useful if you need to use other options during normalization:

ノーマライザーは実際の $value を受け取り、正規化された形式を返します。クロージャーが $options パラメーターも受け取ることがわかります。これは、正規化中に他のオプションを使用する必要がある場合に役立ちます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ...
class Mailer
{
    // ...
    public function configureOptions(OptionsResolver $resolver)
    {
        // ...
        $resolver->setNormalizer('host', function (Options $options, $value) {
            if ('http://' !== substr($value, 0, 7) && 'https://' !== substr($value, 0, 8)) {
                if ('ssl' === $options['encryption']) {
                    $value = 'https://'.$value;
                } else {
                    $value = 'http://'.$value;
                }
            }

            return $value;
        });
    }
}

To normalize a new allowed value in subclasses that are being normalized in parent classes, use addNormalizer() method. This way, the $value argument will receive the previously normalized value, otherwise you can prepend the new normalizer by passing true as third argument.

親クラスで正規化されているサブクラスで新しい許容値を正規化するには、addNormalizer() メソッドを使用します。この方法では、$value 引数は以前に正規化された値を受け取ります。それ以外の場合は、true を 3 番目の引数として渡すことで、新しい正規化子を先頭に追加できます。

Default Values that Depend on another Option

Suppose you want to set the default value of the port option based on the encryption chosen by the user of the Mailer class. More precisely, you want to set the port to 465 if SSL is used and to 25 otherwise.

Mailer クラスのユーザーが選択した暗号化に基づいて、port オプションのデフォルト値を設定するとします。より正確には、SSL を使用する場合はポートを 465 に設定し、それ以外の場合は 25 に設定します。

You can implement this feature by passing a closure as the default value of the port option. The closure receives the options as arguments. Based on these options, you can return the desired default value:

port オプションのデフォルト値としてクロージャを渡すことで、この機能を実装できます。クロージャはオプションを引数として受け取ります。これらのオプションに基づいて、目的のデフォルト値を返すことができます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use Symfony\Component\OptionsResolver\Options;

// ...
class Mailer
{
    // ...
    public function configureOptions(OptionsResolver $resolver)
    {
        // ...
        $resolver->setDefault('encryption', null);

        $resolver->setDefault('port', function (Options $options) {
            if ('ssl' === $options['encryption']) {
                return 465;
            }

            return 25;
        });
    }
}

Caution

注意

The argument of the callable must be type hinted as Options. Otherwise, the callable itself is considered as the default value of the option.

callable の引数は、Options としてタイプ ヒントされている必要があります。それ以外の場合、callable 自体がオプションのデフォルト値と見なされます。

Note

ノート

The closure is only executed if the port option isn't set by the user or overwritten in a subclass.

ポートオプションがユーザーによって設定されていないか、サブクラスで上書きされていない場合にのみ、クロージャが実行されます。

A previously set default value can be accessed by adding a second argument to the closure:

クロージャに 2 番目の引数を追加することで、以前に設定されたデフォルト値にアクセスできます。
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
// ...
class Mailer
{
    // ...
    public function configureOptions(OptionsResolver $resolver)
    {
        // ...
        $resolver->setDefaults([
            'encryption' => null,
            'host' => 'example.org',
        ]);
    }
}

class GoogleMailer extends Mailer
{
    public function configureOptions(OptionsResolver $resolver)
    {
        parent::configureOptions($resolver);

        $resolver->setDefault('host', function (Options $options, $previousValue) {
            if ('ssl' === $options['encryption']) {
                return 'secure.example.org';
            }

            // Take default value configured in the base class
            return $previousValue;
        });
    }
}

As seen in the example, this feature is mostly useful if you want to reuse the default values set in parent classes in sub-classes.

例に見られるように、この機能は、親クラスで設定されたデフォルト値をサブクラスで再利用したい場合に最も役立ちます。

Options without Default Values

In some cases, it is useful to define an option without setting a default value. This is useful if you need to know whether or not the user actually set an option or not. For example, if you set the default value for an option, it's not possible to know whether the user passed this value or if it comes from the default:

場合によっては、デフォルト値を設定せずにオプションを定義すると便利です。これは、ユーザーが実際にオプションを設定したかどうかを知る必要がある場合に便利です。たとえば、オプションのデフォルト値を設定した場合、ユーザーがこの値を渡したのか、それともデフォルトからのものなのかを知ることはできません。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ...
class Mailer
{
    // ...
    public function configureOptions(OptionsResolver $resolver)
    {
        // ...
        $resolver->setDefault('port', 25);
    }

    // ...
    public function sendMail($from, $to)
    {
        // Is this the default value or did the caller of the class really
        // set the port to 25?
        if (25 === $this->options['port']) {
            // ...
        }
    }
}

You can use setDefined() to define an option without setting a default value. Then the option will only be included in the resolved options if it was actually passed to resolve():

setDefined() を使用して、デフォルト値を設定せずにオプションを定義できます。次に、オプションが実際にresolve()に渡された場合にのみ、解決されたオプションに含まれます。
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
// ...
class Mailer
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        // ...
        $resolver->setDefined('port');
    }

    // ...
    public function sendMail($from, $to)
    {
        if (array_key_exists('port', $this->options)) {
            echo 'Set!';
        } else {
            echo 'Not Set!';
        }
    }
}

$mailer = new Mailer();
$mailer->sendMail($from, $to);
// => Not Set!

$mailer = new Mailer([
    'port' => 25,
]);
$mailer->sendMail($from, $to);
// => Set!

You can also pass an array of option names if you want to define multiple options in one go:

一度に複数のオプションを定義したい場合は、オプション名の配列を渡すこともできます:
1
2
3
4
5
6
7
8
9
10
// ...
class Mailer
{
    // ...
    public function configureOptions(OptionsResolver $resolver)
    {
        // ...
        $resolver->setDefined(['port', 'encryption']);
    }
}

The methods isDefined() and getDefinedOptions() let you find out which options are defined:

メソッド isDefined() および getDefinedOptions() を使用すると、どのオプションが定義されているかを確認できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ...
class GoogleMailer extends Mailer
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        parent::configureOptions($resolver);

        if ($resolver->isDefined('host')) {
            // One of the following was called:

            // $resolver->setDefault('host', ...);
            // $resolver->setRequired('host');
            // $resolver->setDefined('host');
        }

        $definedOptions = $resolver->getDefinedOptions();
    }
}

Nested Options

Suppose you have an option named spool which has two sub-options type and path. Instead of defining it as a simple array of values, you can pass a closure as the default value of the spool option with a OptionsResolver argument. Based on this instance, you can define the options under spool and its desired default value:

type と path の 2 つのサブオプションを持つ spool という名前のオプションがあるとします。これを単純な値の配列として定義する代わりに、OptionsResolver 引数を使用してスプール オプションのデフォルト値として aclosure を渡すことができます。このインスタンスに基づいて、スプールの下のオプションとその目的のデフォルト値を定義できます。
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
class Mailer
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefault('spool', function (OptionsResolver $spoolResolver) {
            $spoolResolver->setDefaults([
                'type' => 'file',
                'path' => '/path/to/spool',
            ]);
            $spoolResolver->setAllowedValues('type', ['file', 'memory']);
            $spoolResolver->setAllowedTypes('path', 'string');
        });
    }

    public function sendMail($from, $to)
    {
        if ('memory' === $this->options['spool']['type']) {
            // ...
        }
    }
}

$mailer = new Mailer([
    'spool' => [
        'type' => 'memory',
    ],
]);

Nested options also support required options, validation (type, value) and normalization of their values. If the default value of a nested option depends on another option defined in the parent level, add a second Options argument to the closure to access to them:

ネストされたオプションは、必要なオプション、検証 (タイプ、値)、およびそれらの値の正規化もサポートします。ネストされたオプションのデフォルト値が、親レベルで定義された別のオプションに依存する場合は、クロージャーに 2 番目の Options 引数を追加して、それらにアクセスします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Mailer
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefault('sandbox', false);
        $resolver->setDefault('spool', function (OptionsResolver $spoolResolver, Options $parent) {
            $spoolResolver->setDefaults([
                'type' => $parent['sandbox'] ? 'memory' : 'file',
                // ...
            ]);
        });
    }
}

Caution

注意

The arguments of the closure must be type hinted as OptionsResolver and Options respectively. Otherwise, the closure itself is considered as the default value of the option.

クロージャの引数は、それぞれ OptionsResolver および Options としてタイプ ヒントを指定する必要があります。それ以外の場合、クロージャ自体がオプションのデフォルト値と見なされます。

In same way, parent options can access to the nested options as normal arrays:

同様に、親オプションはネストされたオプションに通常の配列としてアクセスできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Mailer
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefault('spool', function (OptionsResolver $spoolResolver) {
            $spoolResolver->setDefaults([
                'type' => 'file',
                // ...
            ]);
        });
        $resolver->setDefault('profiling', function (Options $options) {
            return 'file' === $options['spool']['type'];
        });
    }
}

Note

ノート

The fact that an option is defined as nested means that you must pass an array of values to resolve it at runtime.

オプションがネストされたものとして定義されているということは、値の配列を渡して実行時に解決する必要があることを意味します。

Prototype Options

There are situations where you will have to resolve and validate a set of options that may repeat many times within another option. Let's imagine a connections option that will accept an array of database connections with host, database, user and password each.

別のオプション内で何度も繰り返される一連のオプションを解決して検証する必要がある場合があります。ホスト、データベース、ユーザー、およびパスワードをそれぞれ持つデータベース接続の配列を受け入れる接続オプションを想像してみましょう。

The best way to implement this is to define the connections option as prototype:

これを実装する最良の方法は、接続オプションをプロトタイプとして定義することです。
1
2
3
4
5
6
$resolver->setDefault('connections', function (OptionsResolver $connResolver) {
    $connResolver
        ->setPrototype(true)
        ->setRequired(['host', 'database'])
        ->setDefaults(['user' => 'root', 'password' => null]);
});

According to the prototype definition in the example above, it is possible to have multiple connection arrays like the following:

上記の例のプロトタイプ定義によると、次のような複数の接続配列を持つことが可能です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$resolver->resolve([
    'connections' => [
        'default' => [
            'host' => '127.0.0.1',
            'database' => 'symfony',
        ],
        'test' => [
            'host' => '127.0.0.1',
            'database' => 'symfony_test',
            'user' => 'test',
            'password' => 'test',
        ],
        // ...
    ],
]);

The array keys (default, test, etc.) of this prototype option are validation-free and can be any arbitrary value that helps differentiate the connections.

このプロトタイプ オプションの配列キー (デフォルト、テストなど) は検証不要であり、接続を区別するのに役立つ任意の値にすることができます。

Note

ノート

A prototype option can only be defined inside a nested option and during its resolution it will expect an array of arrays.

プロトタイプ オプションは、ネストされたオプション内でのみ定義でき、その解決中に配列の配列が必要になります。

Deprecating the Option

Once an option is outdated or you decided not to maintain it anymore, you can deprecate it using the setDeprecated() method:

オプションが古くなった場合、またはそれを維持しないことにした場合は、setDeprecated() メソッドを使用して非推奨にすることができます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$resolver
    ->setDefined(['hostname', 'host'])

    // this outputs the following generic deprecation message:
    // Since acme/package 1.2: The option "hostname" is deprecated.
    ->setDeprecated('hostname', 'acme/package', '1.2')

    // you can also pass a custom deprecation message (%name% placeholder is available)
    ->setDeprecated(
        'hostname',
        'acme/package',
        '1.2',
        'The option "hostname" is deprecated, use "host" instead.'
    )
;

Note

ノート

The deprecation message will be triggered only if the option is being used somewhere, either its value is provided by the user or the option is evaluated within closures of lazy options and normalizers.

非推奨メッセージは、オプションがどこかで使用されている場合にのみトリガーされます。その値がユーザーによって提供されるか、オプションが遅延オプションとノーマライザーのクロージャー内で評価されます。

Note

ノート

When using an option deprecated by you in your own library, you can pass false as the second argument of the offsetGet() method to not trigger the deprecation warning.

独自のライブラリで非推奨のオプションを使用する場合、offsetGet() メソッドの 2 番目の引数として false を渡して、非推奨の警告をトリガーしないようにすることができます。

Instead of passing the message, you may also pass a closure which returns a string (the deprecation message) or an empty string to ignore the deprecation. This closure is useful to only deprecate some of the allowed types or values of the option:

メッセージを渡す代わりに、文字列 (非推奨メッセージ) または空の文字列を返すクロージャーを渡して、非推奨を無視することもできます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$resolver
    ->setDefault('encryption', null)
    ->setDefault('port', null)
    ->setAllowedTypes('port', ['null', 'int'])
    ->setDeprecated('port', 'acme/package', '1.2', function (Options $options, $value) {
        if (null === $value) {
            return 'Passing "null" to option "port" is deprecated, pass an integer instead.';
        }

        // deprecation may also depend on another option
        if ('ssl' === $options['encryption'] && 456 !== $value) {
            return 'Passing a different port than "456" when the "encryption" option is set to "ssl" is deprecated.';
        }

        return '';
    })
;

Note

ノート

Deprecation based on the value is triggered only when the option is provided by the user.

値に基づく非推奨は、オプションがユーザーによって提供された場合にのみトリガーされます。

This closure receives as argument the value of the option after validating it and before normalizing it when the option is being resolved.

このクロージャは、オプションの値を検証した後、オプションが解決されるときに正規化する前に、オプションの値を引数として受け取ります。

Chaining Option Configurations

In many cases you may need to define multiple configurations for each option. For example, suppose the InvoiceMailer class has an host option that is required and a transport option which can be one of sendmail, mail and smtp. You can improve the readability of the code avoiding to duplicate option name for each configuration using the define() method:

多くの場合、オプションごとに複数の構成を定義する必要があります。たとえば、InvoiceMailer クラスに必要なホスト オプションと、sendmail、mail、smtp のいずれかのトランスポート オプションがあるとします。コードの可読性を向上させることができます。 define() メソッドを使用して各構成のオプション名を重複させないようにします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ...
class InvoiceMailer
{
    // ...
    public function configureOptions(OptionsResolver $resolver)
    {
        // ...
        $resolver->define('host')
            ->required()
            ->default('smtp.example.org')
            ->allowedTypes('string')
            ->info('The IP address or hostname');

        $resolver->define('transport')
            ->required()
            ->default('transport')
            ->allowedValues('sendmail', 'mail', 'smtp');
    }
}

Performance Tweaks

With the current implementation, the configureOptions() method will be called for every single instance of the Mailer class. Depending on the amount of option configuration and the number of created instances, this may add noticeable overhead to your application. If that overhead becomes a problem, you can change your code to do the configuration only once per class:

現在の実装では、Mailer クラスのインスタンスごとに configureOptions() メソッドが呼び出されます。オプション構成の量と作成されたインスタンスの数によっては、アプリケーションに顕著なオーバーヘッドが追加される場合があります。そのオーバーヘッドが問題になる場合は、クラスごとに 1 回だけ構成を行うようにコードを変更できます。
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
// ...
class Mailer
{
    private static $resolversByClass = [];

    protected $options;

    public function __construct(array $options = [])
    {
        // What type of Mailer is this, a Mailer, a GoogleMailer, ... ?
        $class = get_class($this);

        // Was configureOptions() executed before for this class?
        if (!isset(self::$resolversByClass[$class])) {
            self::$resolversByClass[$class] = new OptionsResolver();
            $this->configureOptions(self::$resolversByClass[$class]);
        }

        $this->options = self::$resolversByClass[$class]->resolve($options);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        // ...
    }
}

Now the OptionsResolver instance will be created once per class and reused from that on. Be aware that this may lead to memory leaks in long-running applications, if the default options contain references to objects or object graphs. If that's the case for you, implement a method clearOptionsConfig() and call it periodically:

これで、OptionsResolver インスタンスがクラスごとに 1 回作成され、それ以降再利用されます。デフォルトのオプションにオブジェクトまたはオブジェクト グラフへの参照が含まれている場合、実行時間の長いアプリケーションでメモリ リークが発生する可能性があることに注意してください。その場合は、メソッド clearOptionsConfig() を実装し、定期的に呼び出します。
1
2
3
4
5
6
7
8
9
10
11
12
// ...
class Mailer
{
    private static $resolversByClass = [];

    public static function clearOptionsConfig()
    {
        self::$resolversByClass = [];
    }

    // ...
}

That's it! You now have all the tools and knowledge needed to process options in your code.

それでおしまい!これで、コード内のオプションを処理するために必要なすべてのツールと知識が得られました。