Динамические параметры ChoiceType в Symfony 2.8

в моем проекте у меня есть несколько форм с типами выбора с много опций.

Поэтому я решил создать тип выбора автозаполнения на основе автозаполнения jquery, который добавляет новые <option> HTML-элементы к исходным <select> во время выполнения. При выборе они отправляются правильно, но не могут быть обработаны по умолчанию ChoicesToValuesTransformer, так как они не существуют в моей форме, когда я ее создаю.

Как я могу заставить symfony принимать мои динамически добавленные значения?

Я нашел этот ответ Проверка динамически загружаемых вариантов в Symfony 2 , где отправленные значения используются для изменения формы в событии формы PRE_SUBMIT, но не удалось запустить его для моей ситуации. Мне нужно изменить варианты, известные текущему типу, вместо добавления нового виджета в форму


person SBH    schedule 17.02.2016    source источник


Ответы (2)


Для работы с динамически добавляемыми значениями используйте опцию 'choice_loader' типа выбора. Это новое в Symfony 2.7 и, к сожалению, не у меня вообще нет документации.

По сути, это служба, реализующая ChoiceLoaderInterface, которая определяет три функции:

  • loadValuesForChoices(array $choices, $value = null)
    • is called on build form and receives the preset values of object bound into the form
  • loadChoiceList($value = null)
    • is called on build view and should return the full list of choices in general
  • loadChoicesForValues(array $values, $value = null)
    • is called on form submit and receives the submitted data

Теперь идея состоит в том, чтобы сохранить ArrayChoiceList как частную собственность в загрузчике выбора. При сборке вызывается форма loadValuesForChoices(...), здесь мы добавляем все предустановленные варианты в наш список выбора, чтобы их можно было отобразить пользователю. При сборке вызывается loadChoiceList(...), но мы ничего не загружаем, мы просто возвращаем наш собственный список выбора, созданный ранее.

Теперь пользователь взаимодействует с формой, некоторые дополнительные варианты загружаются через автозаполнение и помещаются в HTML. При отправке формы отправляются выбранные значения, и в нашем действии контроллера сначала создается форма, а затем вызывается $form->handleRequest(..) loadChoicesForValues(...), но отправленные значения могут полностью отличаться от тех, которые были включены в начале. Поэтому мы заменяем наш внутренний список выбора новым, содержащим только представленные значения.

Наша форма теперь отлично хранит данные, добавленные автозаполнением.

Хитрость заключается в том, что нам нужен новый экземпляр нашего загрузчика выбора всякий раз, когда мы используем тип формы, иначе внутренний список выбора будет содержать смесь всех вариантов.

Поскольку цель состоит в том, чтобы написать новый тип выбора автозаполнения, вы обычно используете внедрение зависимостей, чтобы передать загрузчик выбора в службу типов. Но для типов это невозможно, если вам всегда нужен новый экземпляр, вместо этого мы должны включать его через параметры. Установка выбора загрузчика в дефолтных опциях не работает, так как они тоже кешируются. Чтобы решить эту проблему, вам нужно написать анонимную функцию, которая должна принимать параметры в качестве параметров:

$resolver->setDefaults(array(
    'choice_loader' => function (Options $options) {
        return AutocompleteFactory::createChoiceLoader();
    },
));

Изменить: вот уменьшенная версия класса загрузчика выбора:

use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;

class AutocompleteChoiceLoader implements ChoiceLoaderInterface
{
    /** @var ChoiceListInterface */
    private $choiceList;

    public function loadValuesForChoices(array $choices, $value = null)
    {
        // is called on form creat with $choices containing the preset of the bound entity
        $values = array();
        foreach ($choices as $key => $choice) {
            // we use a DataTransformer, thus only plain values arrive as choices which can be used directly as value
            if (is_callable($value)) {
                $values[$key] = (string)call_user_func($value, $choice, $key);
            }
            else {
                $values[$key] = $choice;
            }
        }

        // this has to be done by yourself:  array( label => value )
        $labeledValues = MyLabelService::getLabels($values);

        // create internal choice list from loaded values
        $this->choiceList = new ArrayChoiceList($labeledValues, $value);

        return $values;
    }


    public function loadChoiceList($value = null)
    {
        // is called on form view create after loadValuesForChoices of form create
        if ($this->choiceList instanceof ChoiceListInterface) {
            return $this->choiceList;
        }

        // if no values preset yet return empty list
        $this->choiceList = new ArrayChoiceList(array(), $value);

        return $this->choiceList;
    }


    public function loadChoicesForValues(array $values, $value = null)
    {
        // is called on form submit after loadValuesForChoices of form create and loadChoiceList of form view create
        $choices = array();
        foreach ($values as $key => $val) {
            // we use a DataTransformer, thus only plain values arrive as choices which can be used directly as value
            if (is_callable($value)) {
                $choices[$key] = (string)call_user_func($value, $val, $key);
            }
            else {
                $choices[$key] = $val;
            }
        }

        // this has to be done by yourself:  array( label => value )
        $labeledValues = MyLabelService::getLabels($values);

        // reset internal choice list
        $this->choiceList = new ArrayChoiceList($labeledValues, $value);

        return $choices;
    }
}
person SBH    schedule 11.03.2016
comment
О, если бы я только знал, какими должны быть ключи и значения массива! - person Ian Phillips; 19.03.2016
comment
@IanPhillips зависит от того, какой массив вы имеете в виду. Возвращаемые значения функций см. в phpDoc ChoiceLoaderInterface. Ключи всегда такие же, как и в массиве параметров, а значения либо варианты, либо значения. Обратите внимание, что вам по-прежнему нужен преобразователь данных, если вы работаете с сущностями! Массив, используемый для создания внутреннего ArrayChoiceList, должен содержать более поздние метки <option> в качестве ключей и их значения в качестве значений. - person SBH; 19.03.2016
comment
@IanPhillips Я добавил укороченную версию моего загрузчика выбора автозаполнения - person SBH; 19.03.2016
comment
@SBH Что-то странное происходит с моими выбранными значениями. Когда я отправляю ассоциативный массив в свой загрузчик, например [значения => параметры], значения в конечном итоге отображаются как параметры в моей форме, но, по крайней мере, выбранные значения фактически выбираются. Затем, если я переверну массив, параметры отображаются правильно, но они больше не выбираются. - person vctls; 25.01.2017

Базовым (и, вероятно, не лучшим) вариантом было бы отменить сопоставление поля в вашей форме, например:

->add('field', choiceType::class, array(
       ...
       'mapped' => false
    ))

В контроллере после проверки получите данные и отправьте их сущности следующим образом:

$data = request->request->get('field');
// OR
$data = $form->get('field')->getData();
// and finish with :
$entity = setField($data);
person Aridjar    schedule 17.02.2016
comment
Установка 'mapped' => false не решает проблему. Также я ищу общее решение в своем классе пользовательского типа без необходимости добавлять код в любой контроллер. - person SBH; 17.02.2016
comment
Да, вы правы, я перепутал еще одну вещь, которая сочетается со списком событий: динамическая модификация формы, где идея состоит в том, чтобы выбрать все поля в вашей форме и добавить их к вашему выбору - person Aridjar; 17.02.2016