Symfony 2.8动态选择类型选项

Symfony 2.8动态选择类型选项,symfony,symfony-2.8,Symfony,Symfony 2.8,在我的项目中,我有一些带有选项类型的表单,有很多选项 因此,我决定基于jquery autocomplete构建一个autocomplete选择类型,它在运行时将新的HTML元素添加到原始的。选中后,它们将正确提交,但无法在默认的选项ToValuesTransformer中处理,因为创建表单时表单中不存在 如何使symfony接受动态添加的值 我找到了这个答案,其中提交的值用于在PRE_SUBMITform事件中修改表单,但无法针对我的情况运行它。我需要更改当前类型已知的选项,而不是将新的小部件

在我的项目中,我有一些带有选项类型的表单,有很多选项

因此,我决定基于jquery autocomplete构建一个autocomplete选择类型,它在运行时将新的
HTML元素添加到原始的
。选中后,它们将正确提交,但无法在默认的
选项ToValuesTransformer
中处理,因为创建表单时表单中不存在

如何使symfony接受动态添加的值

我找到了这个答案,其中提交的值用于在
PRE_SUBMIT
form事件中修改表单,但无法针对我的情况运行它。我需要更改当前类型已知的选项,而不是将新的小部件添加到表单中

基本(可能不是最佳)选项是取消表单中字段的映射,如:

->add('field', choiceType::class, array(
       ...
       'mapped' => false
    ))
在控制器中,验证后,获取数据并将其发送到实体,如下所示:

$data = request->request->get('field');
// OR
$data = $form->get('field')->getData();
// and finish with :
$entity = setField($data);

要处理动态添加的值,请使用choice类型的
'choice\u loader'
选项。遗憾的是根本没有任何文件

基本上,它是一个实现
ChoiceLoaderInterface
的服务,定义了三个功能:

  • loadValuesForChoices(数组$choices,$value=null)
    • 在生成窗体上调用,并接收绑定到窗体中的对象的预设值
  • loadChoiceList($value=null)
    • 在生成视图上调用,通常应返回完整的选项列表
  • loadChoicesForValues(数组$values,$value=null)
    • 在表单提交时调用,并接收提交的数据
现在的想法是在choice loader中保留一个
ArrayChoiceList
作为私有属性。在生成表单
loadValuesForChoices(…)
中,我们将所有预设选项添加到选项列表中,以便向用户显示它们。在构建视图中调用了
loadChoiceList(…)
,但我们不加载任何内容,只返回以前创建的私有选择列表

现在,用户与表单交互,通过自动完成加载一些附加选项并将其放入HTML中。提交表单时,将提交所选的值,在我们的控制器操作中,首先创建表单,然后在
$form->handleRequest(…)
调用loadChoicesForValues(…)
,但提交的值可能与开头包含的值完全不同。因此,我们用一个只包含提交值的新列表替换我们的内部选择列表

我们的表单现在完美地保存了通过自动完成添加的数据

棘手的部分是,每当我们使用表单类型时,我们都需要选择加载器的一个新实例,否则内部选择列表将包含所有选择的混合

由于目标是编写一个新的自动完成选择类型,您通常会使用依赖项注入将您的选择加载器传递到类型服务中。 但是对于类型,如果您总是需要一个新实例,那么这是不可能的,相反,我们必须通过选项包含它。在默认选项中设置选项加载器不起作用,因为它们也被缓存。要解决此问题,必须编写一个匿名函数,该函数需要将选项作为参数:

$resolver->setDefaults(array(
    'choice_loader' => function (Options $options) {
        return AutocompleteFactory::createChoiceLoader();
    },
));
编辑: 以下是choice loader类的简化版本:

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

设置
'mapped'=>false不能解决问题。另外,我正在我的自定义类型类中寻找一种通用的解决方案,而无需向任何controllerYep添加代码。没错,我混合了另一种与事件列表器相结合的方法:在表单中选择所有字段,并将其添加到ChocieeOh,要是我知道数组键和值应该是什么就好了@IanPhillips取决于您所指的数组。有关函数的返回值,请查看
ChoiceLoaderInterface
的phpDoc。键始终与参数数组中的键相同,值为选项或值。请注意,如果您使用实体,您仍然需要一个!用于创建内部
ArrayChoiceList
的数组应包含以后的
标签作为键,其值作为值。@IanPhillips我添加了一个自动完成选项的缩短版本loader@SBH我选择的价值观有些奇怪。当我像[values=>options]一样向加载程序发送关联数组时,这些值最终显示为表单上的选项,但至少所选的值实际上是选中的。然后,如果翻转阵列,将正确显示选项,但不再选择它们。