如何在未提交子窗体时将其设置为null?

时间:2016-05-18 10:04:27

标签: php symfony symfony-forms

短版

  • 表单数据为:['model' => new TestModel(123)]
  • 使用以下数据提交表单:[]
  • 我的期望:['model' => null]
  • 我得到了什么:['model' => $anEmptyInstanceOfTestModel]

长版

我有一个简单的表格。此表单具有我自定义FormType的子表单(键“模型”)。

如果我使用“模型”已经存在的数据初始化表单,则模型将不为空,即使没有数据提交模型。另一方面,如果init数据对于“model”为null,则“model”的值保持为null。

问题

如果没有提交任何内容,如何配置我的表单以将“model”设置为null?

我已尝试设置'required' => false和/或'empty_data' => null这两项似乎都没有帮助。

单元测试的简单示例

<?php

namespace AppBundle\Tests\Common\Form\Type;

use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class NullModelTest extends TypeTestCase {

    public function testNullOnNotSubmitted() {
        $tm = new TestModel(123);
        $data = ['model' => $tm];
        $form = $this->factory->createBuilder(FormType::class, $data)
                ->add('model', TestModelType::class, ['required' => false])
                ->getForm();
        $form->submit([]); // submit no data
        $this->assertTrue($form->isSynchronized());
        $this->assertNull($form->getData()['model']); // ERROR: returns the empty model
    }

}

class TestModel {
    protected $id;
    public function __construct($id = null) {
        $this->id = $id;
    }
    public function getId() { return $this->id; }
    public function setId($id) { $this->id = $id; }
}

class TestModelType extends AbstractType {
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder->add('id', TextType::class);
    }
    public function configureOptions(OptionsResolver $resolver) {
        $resolver->setDefault('data_class', TestModel::class);
    }
}

2 个答案:

答案 0 :(得分:0)

我不确定它是不是bug,因为这个文档:http://symfony.com/doc/current/cookbook/form/use_empty_data.html#option-2-provide-a-closure说你可以有闭包(可以返回NULL),但它对我也没用。

非常简单的解决方案是使用DataTransformer:

use Symfony\Component\Form\DataTransformerInterface;

class NullToEmptyTransformer implements DataTransformerInterface {
    public function transform($object) {
        return $object;  //DO NOTHING
    }
    public function reverseTransform($object) {
        if (is_null($object->getId()))
            return NULL; //Return NULL if object is empty
        return $object;
    }
}

并将其附加到您的表单字段:

$fb = $this->factory->createBuilder(FormType::class, $data);
$model = $this->factory->createBuilder()
            ->create('model', TestModelType::class, ['required' => false])
            ->addModelTransformer(new NullToEmptyTransformer());
$fb->add($model);
$form = $fb->getForm();

答案 1 :(得分:0)

好的,所以这是一种非常罕见的情况。在&#34;真实&#34; HTML表单没有像#34;它根本没有提交&#34;。 IMO这就是将子表单设置为null这样的功能不存在的原因。此外:子表单可能只代表真实模型的一部分。想象一下,子表单只处理字段&#34; name&#34;一个客户。客户本身有很多领域。在经典HTML表单中,此子表单将呈现为简单的文本字段。现在想象一下,在没有为此子表单提交任何内容之后,您的整个客户将被设置为null。似乎非常错误;)选项empty_data用于在填充数据之前初始化未初始化的对象(例如您的客户) - 而不是替换它(如果它已经存在)。

我达到了这种类型的场景,因为我使用Symfony Forms以安全和结构化的方式使用请求数据填充我的模型。因此,我的请求数据几乎是自定义的(JSON API),整个子表单可以为null或甚至不存在。

目前我使用以下EventSubscriber动态

  1. 查看请求数据是否为空并将其存储以便以后替换数据
  2. 稍后替换数据(因为我需要替换规范数据,而不是请求数据[已经是null])
  3. use Symfony\Component\Form\FormEvents;
    use Symfony\Component\Form\FormEvent;
    use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    
    /**
     * Replaces the data of this form type by the given value if it's not part of the request data.
     */
    class ReplaceIfNotSubmittedListener implements EventSubscriberInterface {
    
        public static function getSubscribedEvents() {
            return [
                FormEvents::PRE_SUBMIT => 'preSubmit',
                FormEvents::SUBMIT => 'submit',
            ];
        }
    
        /**
         * @var bool
         */
        private $shouldBeReplaced = false;
    
        /**
         * @var mixed|callable
         */
        private $replaceValue;
    
        public function __construct($replaceValue) {
            $this->replaceValue = $replaceValue;
        }
    
        public function preSubmit(FormEvent $event) {
            if ($event->getData() === null) {
                $this->shouldBeReplaced = true;
            }
        }
    
        function submit(FormEvent $event) {
            if ($this->shouldBeReplaced) {
                $value = $this->replaceValue;
                $event->setData(is_callable($value) ? $value() : $value);
            }
        }
    
    }
    

    ...以及修改后的主要表单中的用法:

    $fb = $this->factory->createBuilder(FormType::class, $data);
    $model = $this->factory->createBuilder()
                ->create('model', TestModelType::class, ['required' => false])
                ->addEventSubscriber(new ReplaceIfNotSubmittedListener(null));
    $fb->add($model);
    $form = $fb->getForm();