我有一个大实体和一个大形式。更新我的实体时,我只通过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解决吗? (我从未使用过它)
答案 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 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),它呈现复选框或单选按钮,并且需要在提交的有效负载中检查已复选复选框/单选按钮值的简单列表,这个简单的解决方案不起作用。我实现了一个表单扩展,在这些字段上为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相同的方式处理,因此我的侦听器处理两者。选择和多选择开箱即用。