Для работы с динамически добавляемыми значениями используйте опцию '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