【问题标题】:Symfony2 form unchecked checkbox not taken into account, why?Symfony2表单未选中的复选框未考虑在内,为什么?
【发布时间】:2016-05-31 21:20:01
【问题描述】:

当我发送带有未选中复选框的表单时,如果相关实体属性等于true,则它不会更改为false

反之亦然(在发送带有选中复选框的表单时将属性设置为 true)工作正常,以及保存其他字段的所有表单。

这是我如何构建表单并声明相关属性:

// --- Form creation function EntityType::buildForm() ---
$builder->add('secret', 'checkbox', array( 'required' => false ));

// --- Entity related property, Entity.php file ---
/** @ORM\Column(name="secret", type="boolean") */
protected $secret;

编辑:出现问题是因为表单是使用 PATCH 请求提交的。

在 Symfony 中,Form::submit 方法由 Request Handler 使用以下代码行调用:

$form->submit($data, 'PATCH' !== $method);

因此,在 PATCH 请求的情况下,Form::submit$clearMissing 参数设置为 false,从而将未发送的字段保留为旧值。

但是我不知道如何解决这个问题。如果我在未选中复选框时明确地将 JSON {secret: false} 传递给 Symfony 框架,它将把它解释为 "false" 字符串并认为这是一个真值,因此考虑选中复选框...


注意。我有完全相同的问题使用choice 字段类型的复选框数组(使用multipleextendedtrue)链接到Doctrine Simple Array 属性:尽快给定的复选框已被发送一次,因此无法将相关属性设置回 false 以及随后的 unchecked 提交。

【问题讨论】:

    标签: forms symfony checkbox


    【解决方案1】:

    这可能是因为您的架构不需要该字段。您可以使用以下内容为复选框提供默认值:

    $builder->add('secret', 'checkbox', array(
      'required' => false,
      'empty_data' => false
    ));
    

    herehere

    【讨论】:

    • 我已经测试过了,但是没有用。值得一提的是,我通过前端的自定义表单将数据发送到表单。我已经测试了在未选中复选框时显式发送false,但这最终会发送"false"字符串,该字符串被表单解释为true
    • "empty_data" 是组件使用的 php 值,它不会转换为字符串,@Silverspur 你的尝试不同,这可能有效。
    • 我知道这是两个不同的东西。我只是想告诉不仅我按照建议测试了'empty_data'参数的使用,还测试了false值的显式发送:)
    • 实际上,总而言之,empty_data 在这里不起作用,因为当调用 Form::submit 函数并将 $clearMissing 设置为 false 时(这是 PATCH 请求的情况),提交过程甚至不考虑没有发送数据的字段(其中未选中的复选框),因此从不使用此类字段的 empty_data 值。
    • 如果您使用自定义代码提交表单并希望将复选框标记为 false,只需将复选框从发布数据中删除即可。复选框值仅在选中时发送,如果未选中,则根本不会发送复选框。如果您查看CheckboxType::buildForm(),您将看到它仅在给定值时才设置数据,否则为假。 $builder->setData(isset($options['data']) ? $options['data'] : false);。从技术上讲,发送“0”字符串也等于 false:php.net/manual/en/types.comparisons.php
    【解决方案2】:

    您使用的是什么版本的 Symfony?

    vendor/symfony/symfony/src/Symfony/Component/Form/Form.phpForm::submit() 中应该有一些专用于您所写情况的代码:

    // Treat false as NULL to support binding false to checkboxes.
    // Don't convert NULL to a string here in order to determine later
    // whether an empty value has been submitted or whether no value has
    // been submitted at all. This is important for processing checkboxes
    // and radio buttons with empty values.
    if (false === $submittedData) {
        $submittedData = null;
    } elseif (is_scalar($submittedData)) {
        $submittedData = (string) $submittedData;
    }
    

    对我来说位于第 525-534 行。你能检查一下这是否适合你吗?

    另一个线索是自定义表单订阅者,它不能完全按预期工作 - 通过覆盖提供的值。

    【讨论】:

    • 我使用 Symfony 2.6.4 并且在 Form.php 中有这些代码行。但是,使用dump() 可视化$submittedData,它表明$submittedData 在任何submit() 调用中都不会等于false。如果我的复选框没有被选中,submit() 只会被其他表单域调用,而不会被复选框调用。对于选择字段,首先调用submit() 并使用所有选中选项['opt1', 'opt2',etc.] 的数组,然后分别使用每个选项字符串(首先是'opt1',然后是'opt2',等等),但从不用于未选中的选项,并且仍然永远不要使用 false 值。
    • 保持相同的方法,我建议手动调试。 Symfony 表单组件可能非常棘手,并且很难远程查明您的问题。检查退出 PRE_SUBMIT 事件时是否以正常方式修改表单值,检查复选框的父表单是否为 compound ($this->config->getCompound()) 以及是否调用了 $child->submit()
    • 我找到了问题(参见我自己的答案)。感谢您指出解决问题的正确方法:手动调试^^
    【解决方案3】:

    出现问题是因为表单是使用PATCH 请求提交的。

    这导致打开this Symfony issue

    如上所述,一种解决方法是在未选中复选框时显式发送特定的保留值(例如字符串“__false”)(而不是发送任何内容),并使用自定义数据转换器将此值替换为“null”在表单类型中:

    // MyEntityFormType.php -- buildForm method
    $builder->add('mycheckbox', ...);
    $builder->get('mycheckbox')
        ->addViewTransformer(new CallbackTransformer(
            function ($normalizedFormat) {
                return $normalizedFormat;
            },
            function ($submittedFormat) {
                return ( $submittedFormat === '__false' ) ? null : $submittedFormat;
            }
        ));
    

    “选择”字段的情况不能以同样的方式解决。其实是Symfony的一个bug,在this issue处理过。

    【讨论】:

    【解决方案4】:

    上述没有对我有帮助。 所以,我正在使用这个......

    说明

    当使用“PATCH”方法时,此问题的解决方案是在表单类型内添加额外的隐藏“时间戳”字段,并将其放在 twig 文件中问题的复选框旁边。这需要与复选框一起传递一些东西,这肯定会改变 - 时间戳会改变。

    接下来是使用 PRE_SUBMIT 事件并等待表单字段到达,如果未设置,我将手动设置它...工作正常,我不介意额外代码...

    表单类型

    $builder
    ...
    ->add('some_checkbox')
    ->add('time_stamp', 'hidden', ['mapped' => false, 'data' => time()])
    ...
    

    树枝

    {{ form_widget(form.time_stamp) }}
    {{ form_widget(form.some_checkbox) }}
    

    构建器中的 PRE_SUBMIT 事件

    $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use($options) {
    
        $data = $event->getData();
        $form = $event->getForm();
    
        if (!$data) {
            return;
        }
        /* Note that PATCH method is added as an option for "createForm" 
         * method, inside of your controller 
        */       
        if ($options["method"]=="PATCH" && !isset($data['some_checkbox'])) {
            $form->getData()->setSomeCheckbox(false);//adding missing checkbox, since it didn't arrive through submit.. grrr
        }            
    });
    

    【讨论】:

    • $form->get('someCheckbox')->setData(false) 而不是 $form->getData()->setSomeCheckbox(false),否则更新后你仍然会看到选中的复选框跨度>
    • @VictorBredihin 它以前工作过,所以我怀疑它与 Symfony 的版本有关。你用哪一个?应更新答案以提供两者的解决方案。这是 Symfony 2。
    • 问题在 Symfony 5.3 中仍然存在
    【解决方案5】:

    这个解决方案对我有用。

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('isActive', CheckboxType::class, array(
                'required' => false
            ))
        $builder->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $e {
            $entity = $e->getData();
            $form = $e->getForm();
            $isActive = empty($_POST[$form->getName()]['isActive']) ? false : true;
            $entity->setIsActive($isActive);
        });
    }
    

    【讨论】:

      【解决方案6】:

      另一种可能性是添加一个隐藏元素并使用 Javascript 制作它。它会在 0.1% 的使用没有 javascript 的浏览器的人中失败。 这是一个多复选框 FormType 元素的简单示例:

                  ->add('ranges', ChoiceType::class, array(
                  'label'   => 'Range',
                  'multiple' => true,
                  'expanded' => true,
                  'choices' => array(
                      null => 'None',
                      'B1' => 'My range',
                  )
              ))
      
      <script>
      $(document).ready(function() {
             function updateDynRanges(object) {
                  if (object.prop("checked")) {
                      document.getElementById('ranges_0').checked = 0;
                  } else {
                      document.getElementById('ranges_0').checked = 1;
                  }
              }
       // On page onload
              $('#ranges_1').change(function() {
                 updateDynRanges($(this));
              });
              updateDynRanges($('ranges_1'));
      }
      </script>
      

      如果测试成功后,您可以在第二个复选框中添加可见性:false。

      树枝模板:

       {{ form_label(form.dynamicRanges) }}<br>
       {{ form_widget(form.dynamicRanges[1]) }}
       <div class="hidden">{{ form_widget(form.ranges[0]) }}</div>
      

      看起来像一个丑陋的解决方法,但我只是想与其他丑陋的建议解决方法竞争,在这种情况下主要是更新树枝模板。

      【讨论】:

        猜你喜欢
        • 2021-02-05
        • 1970-01-01
        • 1970-01-01
        • 2012-10-28
        • 2021-04-07
        • 1970-01-01
        • 2016-03-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多