Компонент OptionsResolver (Розвʼязувач опцій)

Дата оновлення перекладу 2022-10-29

Компонент OptionsResolver (Розвʼязувач опцій)

Компонент OptionsResolver - это улучшенная замена PHP-функции array_replace. на стероидах. Он позволяет вам создавать систему опций с обязательными опциями, значениями по умолчанию, валидацией (типа, значения), нормализаицей и больше.

Установка

1
$ composer require symfony/options-resolver

Note

Якщо ви встановлюєте цей компонент поза додатком Symfony, вам потрібно підключити файл vendor/autoload.phpу вашому коді для включення механізму автозавантаження класів, наданих Composer. Детальніше можна прочитати у цій статті.

Використання

Уявіть, що у вас є клас Mailer, який має чотири опції: host, username, password и port:

1
2
3
4
5
6
7
8
9
class Mailer
{
    protected $options;

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

При отриманні доступу до $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);

        // ...
    }
}

Також, значення опцій за замовчуванням закопуються під бізнес-логіку вашого кода. Використайте 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);
    }
}

Тепер всі чотири опції точно будуть встановлені, але ви все ще можете зробити помилу, на кшталт наступної, використовуючи клас Mailer:

1
2
3
$mailer = new Mailer([
    'usernme' => 'johndoe',  // 'username' помилково написано як 'usernme'
]);

Помилка не буде відображена. В кращому випадку, баг проявиться під час тестування, але розробник витратить час на пошук проблеми. В гіршому випадку - баг не проявиться, поки не буде розгорнутий у живій системі.

На щастя, клас 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);
    }
}

Як і раніше, всі опції будуть обовʼязково встановлені. Крім того, викликається UndefinedOptionsException, якщо передається невідома опція:

1
2
3
4
5
6
$mailer = new Mailer([
    'usernme' => 'johndoe',
]);

// UndefinedOptionsException: Опція "usernme" не існує.
// Відомі опції: "host", "password", "port", "username"

Залишок вашого кода може отримати доступ до значень опцій без рутинного кода:

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']);
        // ...
    }
}

Гарною практикою є розділення конфігурації опції в окремі методи:

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,
        ]);
    }
}

По-перше ваш код стає простіше читати, особливо, якщо конструктор робить більше, ніж просто обробляє опції. По-друге, підкласи тепер можуть перевизначати метод 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',
        ]);
    }
}

Обовʼязкові опції

Якщо опція повинна бути встановлена ініціатором виклику, передайте цю опцію методу setRequired(). Наприклад, щоб зробити опцію host обовʼязковою, ви можете:

1
2
3
4
5
6
7
8
9
10
11
// ...
class Mailer
{
    // ...

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

Якщо ви опустите обовʼязкову опцію, буде викликано MissingOptionsException:

1
2
3
$mailer = new Mailer();

// MissingOptionsException: Обовʼязкова опція "host" відсутня.

Метод setRequired() приймає одне імʼя або масив імен опцій, якщо у вас більше однієї обовʼязкової опції:

1
2
3
4
5
6
7
8
9
10
11
// ...
class Mailer
{
    // ...

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

Використайте 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();
    }
}

Якщо ви хочете перевірити, чи все ще відсутня обовʼязкова опція в опціяї за замовчуванням, ви можете використати 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
    }
}

Метод getMissingOptions() дозволяє вам отримати доступ до імен всіх відсутніх опцій.

Валідація типу

Ви можете провести додаткові перевірки опцій, щоб переконатися, що вони були передані правильно. Щоб валідувати типи опцій, викличте 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)
    {
        // ...

        // вкажіть один дозволений тип
        $resolver->setAllowedTypes('host', 'string');

        // вкажіть декілька дозволених типів
        $resolver->setAllowedTypes('port', array('null', 'int'));

        // рекурсивно перевірте всі обʼєкти у масиві на тип
        $resolver->setAllowedTypes('dates', 'DateTime[]');
        $resolver->setAllowedTypes('ports', 'int[]');
    }
}

Ви можете передати будь-який тип, для якого функція is_<type>() визначена в PHP. Ви можете також передати повне імʼя класу або інтерфейсу (яке перевіряється, використовуючи instanceof). Крім того, ви можете валідувати всі обʼєкти в масиві рекурсивно, додавши до типу суфікс [].

Якщо ви зараз передасте невалідну опцію, буде викликано InvalidOptionsException:

1
2
3
4
5
6
$mailer = new Mailer([
    'host' => 25,
]);

// InvalidOptionsException: Опція "host" зі значенням "25" повинна
// мати тип "string"

У підкласах ви можете використати addAllowedTypes(), щоб додати додаткові дозволені типи не видаляючи ті, що вже встановлені.

Валідація значення

Деякі опції можуть викристовувати лише один зі списків визначених значень. Наприклад, уявіть, що клас Mailer має опцію transport, яка може бути одним з sendmail, mail та smtp. Використайте метод setAllowedValues(), щоб переконатися, що передана опція містить одне з цих значень:

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']);
    }
}

Якщо ви передасте невалідний транспоорт, буде викликано InvalidOptionsException:

1
2
3
4
5
6
$mailer = new Mailer([
    'transport' => 'send-mail',
]);

// InvalidOptionsException: Опція "transport" має значення
// "send-mail", але повинна бути одним з "sendmail", "mail", "smtp"

Для опцій зі складнішими схемами валідації, передайте завершувач, який повертає true для прийнятних значень, і false - для невалідних:

1
2
3
4
// ...
$resolver->setAllowedValues('transport', function ($value) {
    // повернути true або false
});

Tip

Ви навіть можете використати компонент Валидатор, щоб валідувати введення, використовуючи метод createIsValidCallable():

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

У підкласах ви можете використати addAllowedValues(), щоб додати додаткові дозволені значення, не стираючи вже встановлені.

Нормалізація опцій

Інколи, значення опцій потрібно нормалізувати перед тим, як викорисовувати. Наприклад, уявіть, що host повинен завжди починатися з 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;
        });
    }
}

Нормалізатор отримує справжнє $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;
        });
    }
}

Щоб нормалізувати нове дозволене значення у субкласах, які нормалізуються у батьківських класах, використайте addNormalizer(). Таким чином, аргумент $value буде отримувати раніше нормалізоване значення, або ж ви можете додати до початку нового нормалізатора, передавши true в якості третього агументу.

Значення за замовчуванням, залежні від іншої опції

Уявіть, що ви хочете встановити значення за замовчуванням для опції port, засноване на шифруванні, вибраному користувачем класу Mailer. Точніше, ви хочете встановити порт 465, якщо використовується SSL, і 25 - в інших випадках.

Ви можете реалізувати цю функцію, передавши завершувач в якості значення за замовчуванням опції 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

Аргумент викликаного повинен бути типізований як Options. Інакше саме викликане розглядається як значення опції за замовчуванням.

Note

Завершувач виконується лише якщо опція port не встановлена користувачем або перезаписана у підкласі.

Доступ до раніше встановленого значення за замовчуванням пожна отримати, додавши до завершувача другий аргумент:

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';
            }

            // Взяти значення за замовчуванням, сконфігуроване у базовому класі
            return $previousValue;
        });
    }
}

Як можна побачити у прикладі, ця функція найкорисніша, якщо ви хочете повторно використати значення за замовчуванням, встановлені у батьківських класах або підкласах.

Опції без значень за замовчуванням

У деяких випадках, корично визначати опцію, не встановлюючи значення за замовчуванням. Це корисно, якщо вам потрібно знати, чи дійсно користувач встановив опцію. Наприклад, якщо ви встановите значення за замовчуванням для опції, неможливо дізнатися, чи передав користувач значення, чи воно просто є значенням за замовчуванням:

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)
    {
        // Це значення за замовчуванням або ініціатор класу дійсно встановив
        // порт 25?
        if (25 === $this->options['port']) {
            // ...
        }
    }
}

Ви можете використати 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);
// => Не встановлено!

$mailer = new Mailer([
    'port' => 25,
]);
$mailer->sendMail($from, $to);
// => Встановлено!

Ви також можете передати масив імен опцій, якщо ви хочете визначати декілька опцій за один раз:

1
2
3
4
5
6
7
8
9
10
// ...
class Mailer
{
    // ...
    public function configureOptions(OptionsResolver $resolver)
    {
        // ...
        $resolver->setDefined(['port', 'encryption']);
    }
}

Методи 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')) {
            // Было вызвано одно из следующих:

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

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

Вкладені опції

Уявіть, що у вас є опція під назвою spool, яка має дві підопції - type і path.
Замість того, щоб визначати її як простий масив значень, ви можете передати замикання в якості значення за амовчуванням опції spool з аргументом OptionsResolver. Засновуючись на цьому екземплярі, ви можете визначити опції під spool та його бажане значення за замовчуванням:

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',
    ],
]);

Вкладені опції також підтримують обовʼязкові опції, валідацію (типа, значення) та нормалізацію їх значень. Якщо значення за замовчуванням вкладеної опції залежить від іншої опції, визначеної на батьківському рівні, додайте другий аргумент 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

Аргументи замикання повинні мати підказки OptionsResolver та Options, відповідно. Інашке, саме замикання буде вважатися значенням за замовчуванням.

Таким же чином, батьківські опції можуть отримати доступ до вкладених опцій, як до нормальних масивів:

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

Те, що опція визначена як вкладена, означає, що ви повинні передати масив значень, щоб розвʼязати їх під час прогону.

Прототипи опцій

5.3

Прототипи опцій були представлені в Symfony 5.3.

Бувають ситуації, коли вам потрібно буде розвʼязаи та валідувати набір опцій, які можуть повторюватися багато разів в іншій опції. Давайте уявимо опцію connections, яка прийматиме масив зʼєднань бази даних з host, database,
user і password в кожній.

Найкращий спосіб реалізувати це - визначити опцію connections в якості прототипу:

1
2
3
4
5
6
$resolver->setDefault('connections', function (OptionsResolver $connResolver) {
    $connResolver
        ->setPrototype(true)
        ->setRequired(['host', 'database'])
        ->setDefaults(['user' => 'root', 'password' => null]);
});

У відповідності з визначенням прототипу у прикладі вище, можливо мати багато масивів зʼєднання на кшталт наступного:

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',
        ],
        // ...
    ],
]);

Ключі масиву (default, test, та ін.) цього прототипу опції не підлягають валідації і можуть бути будь-яким довільним значенням, що допомагає диференціювати зʼєднання.

Note

Прототип опції може бути визначений лише всередині вкладеної опції, та під час її розвʼязання від буде очікувати масив масивів.

Старіння опції

5.1

Підпис методу setDeprecated() змінився з setDeprecated(string $option, ?string $message) на setDeprecated(string $option, string $package, string $version, $message) в Symfony 5.1.

Як тільки опція застаріла або ви вирішили її більше не підтримувати, ви можете відмітити її застарілою, використовуючи метод setDeprecated():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$resolver
    ->setDefined(['hostname', 'host'])

    // це виведе наступне загальне повідомлення про старіння:
    // Починаючи з acme/пакета 1.2: Опція "hostname" застаріла.
    ->setDeprecated('hostname', 'acme/package', '1.2')

    // Ви можете також передати ваше власне повідомлення про старіння (доступний заповнювач %name%)
    ->setDeprecated(
        'hostname',
        'acme/package',
        '1.2',
        'The option "hostname" is deprecated, use "host" instead.'
    )
;

Note

Повідомлення про старіння буде запущене лише якщо опція десь використовується або її значення надане користувачем, або опція оцінюється в рамках замикань лінивих опцій та нормалізаторів.

Note

При використанні опції, відміченої застаролою вами у власній бібліотеці, ви можете передати false в якості другого аргументу методу offsetGet(), щоб не викликати попередження про старіння.

Замість передачі повідомлення ви можете також передати замикання, яке поверне рядок (повідомлення про старіння) або пустий рядок для ігнорування старіння. Це замикання зручне для старіння лише деяких типів або значень опції:

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.';
        }

        // старіння також може залежати від іншої опції
        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

Старіння, засноване на значенні, запускається лише тоді, коли опція надається користувачем.

Це замикання отримує в якості другого аргументи значення опції після її валідації та перед її нормалізацією, коли опція розвʼязується.

Створення ланцюжку конфігурацій опції

У багатьох випадках вам може знадобитися визначити багато конфігурацій для кожної опції. Наприклад, уявіть, що клас InvoiceMailer має обовʼязкову опцію host та опцію transport, яка може бути 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');
    }
}

5.1

Методи define() та info() були представлені в Symfony 5.1.

Налаштування продуктивності

З поточною реалізацією, метод configureOptions() викликатиметься для кожного екземпляра класу Mailer. В залежності від обсягу конфігурації опції та кількості створених екземплярів, це може створювати додаткове навантаження на ваш додаток. Якщо це навантаження стане проблемою, ви можете змінити ваш код, щоб конфігурація робилась лише один раз для одного класу:

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 = [])
    {
        // Який це тип Mailer: Mailer, GoogleMailer, ... ?
        $class = get_class($this);

        // Чи була виконана configureOptions() до цього класу?
        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)
    {
        // ...
    }
}

Тепер екземпляр OptionsResolver буде створений один раз для одного класу і далі використаний повторно. Майте на увазі, що це може призвести до пробілів в памʼяті у довгострокових додатках, якщо опції за замовчуванням містять посилання на обʼєкти або графіки обʼєктів. Якщо це такий випадок, реалізуйте метод clearOptionsConfig() та періодично викликайте його:

1
2
3
4
5
6
7
8
9
10
11
12
// ...
class Mailer
{
    private static $resolversByClass = [];

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

    // ...
}

Ось і все! Тепер у вас є всі інструменти та знання, необхідні для легкої обробки опції у вашому коді.