Обзор компонентов Symfony2 : OptionsResolver

Проблема

Я думаю, вы не раз писали что-то подобное:

$options = [
    'page' => isset($input['page']) ? $input['page'] : 1,
    'items' => isset($input['items']) ? $input['items'] : 10
];

Нам требуется массив пользовательских настроек. В случае отсутствия каких-либо опций мы используем значения по умолчанию. Поэтому, проверяем заданы ли настройки, если нет, то берем значения по умолчанию. Согласитесь, такой код очень плохо читается и занимает довольно много места, так как повторяется этот участок, как правило, далеко не один раз.

Одно из решений - использовать array_replace:

$options = array_replace([
    'page'  => 1,
    'items' => 10
], $input);

array_replace заменяет значения первого аргумента на значения второго. Таким образом если $input уже содержит какие-либо значения, они будут переписаны.

В целом, такое решение вполне применимо. Но не спешите с выводами, обратите внимание сначала на OptionsResolver. В дополнение к значениям по умолчанию, он также может проверить и нормализовать данные.

Простой пример

Решим предыдущую задачу при помощи OptionsResolver:

use Symfony\Component\OptionsResolver\OptionsResolver;

$resolver = new OptionsResolver();

$resolver->setDefaults([
    'page' => 1,
    'items' => 10
]);

$options = $resolver->resolve($input);

После выполнения, $options получит простой массив с двумя элементами: page и items. Если $input уже содержит какие-либо из этих значений, то они будут переписаны.

Разберем еще несколько примеров:

$input = []; // 'page' => 1, 'items' => 10
$input = ['page' => 2]; // 'page' => 2, 'items' => 10
$input = ['page' => 2, 'items' => 20]; // 'page' => 2, 'items' => 20
$input = ['other' => 5]; // UndefinedOptionsException

Если попробуем применить значение не заданное ранее, то мы получим исключение. Уже это можно считать превосходством OptionsResolver над array_replace, но на этом его преимущества не заканчиваются.

Допускается использование замыкания для установки значений по умолчанию в случае, когда они зависит от чего-то еще. Мы даже можем задавать одни параметры в зависимости от других. Например, давайте создадим третий параметр order, значение которого будет выбрано случайным образом (улучшим взаимодействие с пользователем).

$resolver->setDefault('order', function (Options $options) {
    $orders = ['asc', 'desc'];

    return $orders[rand(0, 1)];
});

Как видите, функция получает объект Options в качестве аргумента, то есть мы можем его использовать для генерации значений, зависящих от других настроек. Допустим в настройках у нас присутствует параметр order_by, тогда будем устанавливать order исходя из него.

$resolver->setDefault('order', function (Options $options) {
    if ('creation_date' === $options['order_by']) {
        return 'desc';
    }

    return 'asc';
});

Проверка параметров

Одной из часто встречающихся задач при обработки данных, которые вводит пользователь, является проверка этих данных. OptionsResolver уже имеет в своем составе методы для проверки значений: setAllowedTypes() и setAllowedValues().

Метод setAllowedTypes() помогает ограничить круг типов данных, которые может принимать параметр. Например, параметр page должен иметь значения только типа integer:

$resolver->setAllowedTypes('page', 'int');

Также можно указать несколько типов:

$resolver->setAllowedTypes('page', ['int', 'float']);

А что если нам требуется ограничить диапазон значений? Метод setAllowedValues() поможет нам в этом. Например, ограничим параметр items значениями 10, 20, 40:

$resolver->setAllowedValues('items', [10, 20, 40]);

Но суть этого метода заключается в том, что он может принимать замыкание в качестве аргумента. В следующем примере мы проверим что значение page между 1 и 10:

$resolver->setAllowedValues('page', function($value) {
    return $value >= 1 && $value <=10;
});

Если вы уже знакомы с компонентом Validator, то вы знаете, что он содержит встроенное требование подобного типа. Применить этот компонент можно следующим образом:

use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints\Range;

$resolver->setAllowedValues('items', function($value) {
    $validator = Validation::createValidator();
    $constraint = new Range([
        'min' => 1,
        'max' => 10
    ]);

    $violations = $validator->validate($value, $constraint);

    return 0 === $violations->count();
});

Нормализация

Мы также можем нормализовать данные перед их использованием при помощи метода setNormalizer(). Он принимает замыкание и все настройки. Например, мы можем изменить значение параметра page в случае, если пользователь указал значение больше, чем количество страниц:

$resolver->setNormalizer('page', function ($options, $value) use ($maxPage) {
    if ($value > $maxPage) {
        $value = $maxPage;
    }

    return $value;
});

Статьи из серии

Комментарии

0