【问题标题】:Symfony 2.8 dynamic ChoiceType optionsSymfony 2.8 动态 ChoiceType 选项
【发布时间】:2016-05-29 03:03:43
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: symfony symfony-2.8


    【解决方案1】:

    要处理动态添加的值,请使用选择类型的'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;
        }
    }
    

    【讨论】:

    • 哦,要是我知道数组键和值应该是什么就好了!
    • @IanPhillips 取决于您的意思是哪个数组。对于函数的返回值,请查看ChoiceLoaderInterface 的 phpDoc。键始终与参数数组和值中的选择或值相同。请注意,如果您使用实体,您仍然需要DataTransformer!用于创建内部ArrayChoiceList 的数组应包含后面的<option> 标签作为键,它们的值作为值。
    • @IanPhillips 我添加了我的自动完成选择加载器的缩短版本
    • @SBH 我选择的值有些奇怪。当我将关联数组发送到我的加载器(如 [values => options])时,这些值最终显示为我的表单上的选项,但至少,实际选择了所选值。然后,如果我翻转数组,选项会正确显示,但不再选择它们。
    【解决方案2】:

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

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

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

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

    【讨论】:

    • 设置'mapped' => false 并不能解决问题。此外,我正在我的自定义类型类中寻找通用解决方案,而无需向任何控制器添加代码
    • 是的,你是对的,我混合了另一个与事件列表器相结合的东西:dynamic form modification 其想法是在表单中选择所有字段,并将其添加到你的选择
    猜你喜欢
    • 1970-01-01
    • 2017-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多