Symfony3:如何使用PATCH方法将布尔值更新为false

时间:2016-02-03 21:15:13

标签: symfony

我有一个大实体和一个大形式。更新我的实体时,我只通过ajax调用渲染表单的一部分。在客户端,我使用Jquery和 html5 FormData ,所以我也可以在我的表单中发送文件。为了确保在此过程中未呈现的字段不会设置为null,我使用 PATCH方法

因此,当请求中的某个字段不存在时,Symfony会将保留为

但是当我更新的字段是一个设置为true的布尔值(渲染aa复选框)并且我想将其设置为false时,它不会在请求中传递,因此我的更新被忽略

是否有一种简单的方法可以强制取消选中复选框以显示在请求中?

修改
感谢Felix Kling对this question的评论,我找到了一种强制未选中复选框显示在请求中的方法:

$("input:checkbox:not(:checked)").each(function() {
    formData.set($(this).attr('name'), formData.has($(this).attr('id')) ? '1' : '0');
});  

不幸的是,这并没有解决我的问题,因为Symfony的行为:
- 使用 PUT 时,如果请求中出现布尔字段,则设置为true,设置为(即使它为“0”或“false”)。
- 使用 PATCH 方法时,将忽略请求中未出现的字段。

可以用DataTransformer解决吗? (我从未使用过它)

3 个答案:

答案 0 :(得分:5)

你是绝对正确的,如果因为this line in Request Handler而方法是PATCH,Symfony会忽略它:

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

现在,我建议您使用PUT请求,如果这是一个选项,但如果它不是,那么FormInterface::submit($submittedData, $clearMissing = true)的第二个参数就是您之后的选择

"适当"方法可能是自己实施Symfony\Component\Form\RequestHandlerInterface,这将迫使$clearMissing成为true

其他,方式更容易,但可能不适用于所有用例:直接使用$form->submit()

如果您有以下代码:

$form->handleRequest($request);

你可以这样做:

$form->submit($request->get($form->getName()), true);

您还可以省略第二个参数,因为true是默认值

答案 1 :(得分:1)

这是一个有效的解决方案,可以改进

要强制取消选中复选框以显示在请求中,感谢Felix Kling对this question的评论,我在我的ajax请求之前添加了这个js:

$("input:checkbox:not(:checked)").each(function() {
    formData.set($(this).attr('name'), formData.has($(this).attr('id')) ? '1' : '0');
});  

然后,在Symfony端,我必须覆盖BooleanToStringTransformer行为,它返回true表示任何字符串,false仅表示null值。如果值与 true (默认为“1”)定义的值不匹配,我们现在返回 false 的最后一行。因此,如果表单返回的值为“0”,我们会按预期获得 false

public function reverseTransform($value)
{
    if (null === $value) {
        return false;
    }

    if (!is_string($value)) {
        throw new TransformationFailedException('Expected a string.');
    }

    return ($this->trueValue === $value); // initially: "return true;" 
}

关注the docs,我创建了自己的DataTransformer,以及自定义 AjaxCheckboxType

不幸的是,似乎Symfony一个接一个地使用两个 DataTransformers(我的和原始的),所以它不起作用。在他们扩展TextType而不是CheckboxType的文档中,这必须解释我遇到的问题。

我最终在我自己的AjaxCheckboxType中复制并粘贴整个CheckboxType class,只更改了DataTransformer的调用以便使用我的。{/ p>

一个更好的解决方案是完全覆盖DataTransformer,但我不知道如何。

答案 2 :(得分:1)

Symfony开箱即用,只需正确准备PATCH-payload:)

Symfony CheckboxType,至少在当前版本3.3中(似乎自2.3以来,见下面的更新),接受输入值null,解释为“未选中”(如第3行所示) Roubi's really helpful answer)中的代码段的-5。

因此,在您的客户端AJAX-PATCH控制器中,您将application/merge-patch+json有效负载中(脏)未选中复选框字段的值设置为null,一切正常。没有任何表单扩展名覆盖CheckboxType所需的行为。

问题是:我认为,您无法将HTTP-POST-payload的值设置为null,因此这仅适用于请求正文中的JSON(或其他兼容的)有效负载。

一个简单的演示

为了证明这一点,您可以使用这个简化的测试控制器:

/**
 * @Route("/test/patch.json", name="test_patch")
 * @Method({"PATCH"})
 */
public function patchAction(\Symfony\Component\HttpFoundation\Request $request)
{
    $form = $this->createFormBuilder(['checkbox' => true, 'dummyfield' => 'presetvalue'], ['csrf_protection' => false])
        ->setAction($this->generateUrl($request->get('_route')))
        ->setMethod('PATCH')
        ->add('checkbox', \Symfony\Component\Form\Extension\Core\Type\CheckboxType::class)
        ->add('dummyfield', \Symfony\Component\Form\Extension\Core\Type\TextType::class)
        ->getForm()
    ;
    $form->submit(json_decode($request->getContent(), true), false);

    return new \Symfony\Component\HttpFoundation\JsonResponse($form->getData());
}

对于Content-Type: application/merge-patch+json的PATCH请求,或者在这种情况下也是任何有效的JSON-payload,将发生以下情况:

使用null

提交复选框
{"checkbox": null}

会将复选框覆盖为false:

{"checkbox": false, "dummyfield": "presetvalue"}

并提交带有原始值的复选框

{"checkbox": "1"}

将复选框设置为true(之前也是如此)

{"checkbox": true, "dummyfield": "presetvalue"}

并且没有提交复选框的值

{"dummyfield": "requestvalue"}

将使复选框保持其初始状态,并仅覆盖虚拟字段:

{"checkbox": true, "dummyfield": "requestvalue"}

这是PATCH请求应该如何工作,不需要额外的隐藏输入。只需在客户端准备好你的JSON-payload,就可以了。

好的,但是扩展的ChoiceType / EntityType呢?

对于扩展的ChoiceType(或其子类型,如EntityType),它呈现复选框或单选按钮,并且需要在提交的有效负载中检查已复选复选框/单选按钮值的简单列表,这个简单的解决方案不起作用。我实现了一个表单扩展,在这些字段上为PRE_SUBMIT添加了一个事件监听器,将非提交的复选框/ radiobuttons设置为null。必须在CheckboxType的闭包侦听器之后调用此事件侦听器,将简单列表["1", "3"]传输到具有checkbox-values作为键和值的哈希。 -1的优先级为我工作。所以["1" => "1", "3" => "3"]从我的听众那里得到["1" => "1", "2" => null, "3" => "3"]。我PatchableChoiceTypeExtension的听众看起来基本上是这样的:

$builder->addEventListener(
    \Symfony\Component\Form\FormEvents::PRE_SUBMIT,
    function (\Symfony\Component\Form\FormEvent $event) {
        if ('PATCH' === $event->getForm()->getRoot()->getConfig()->getMethod()
            && $event->getForm()->getConfig()->getOption('expanded', false)
        ) {
            $data = $event->getData();
            foreach ($event->getForm()->all() as $type) {
                if (!array_key_exists($type->getName(), $data)) {
                    $data[$type->getName()] = null;
                }
            }
            ksort($data);
            $event->setData($data);
        }
    }, -1
);

更新:在/Symfony/Component/Form/Form.php中的submit-method中查看此注释(自Symfony 2.3以来就是这样):

// 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.

更新2017-09-12:Radiogroups必须以与Checkboxgroups相同的方式处理,因此我的侦听器处理两者。选择和多选择开箱即用。