Symfony 2.8动态ChoiceType选项

时间:2016-02-17 12:02:58

标签: symfony symfony-2.8

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

所以我决定基于jquery自动完成构建一个自动完成选择类型,它在运行时将新的<option> HTML元素添加到原始<select>。选择后,它们会被正确提交,但无法在默认ChoicesToValuesTransformer内处理,因为我创建时不会在我的表单中存在。

如何让symfony接受我动态添加的值?

我找到了这个答案Validating dynamically loaded choices in Symfony 2,其中提交的值用于修改PRE_SUBMIT表单事件中的表单,但无法让我的情况运行。我需要更改当前类型已知的选择,而不是将新小部件添加到表单

2 个答案:

答案 0 :(得分:21)

要处理动态添加的值,请使用选择类型的'choice_loader'选项。 It's new in symfony 2.7遗憾的是根本没有任何文件。

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

  • loadValuesForChoices(array $choices, $value = null)
    • 在构建表单上调用,并接收绑定到表单
    • 中的对象的预设值
  • loadChoiceList($value = null)
      在构建视图中调用
    • ,并且应该返回一般选择的完整列表
  • loadChoicesForValues(array $values, $value = null)
    • 在表单提交时调用并接收提交的数据

现在的想法是在选择加载器中保留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;
    }
}

答案 1 :(得分:0)

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

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

在控制器中,验证后,获取数据并将其发送到实体,如下所示:

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