【问题标题】:How to set a subform to null when it's not submitted?未提交时如何将子表单设置为空?
【发布时间】:2016-05-18 10:04:27
【问题描述】:

短版

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

加长版

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

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

问题

如果没有提交任何内容,如何配置我的表单以将“模型”设置为 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);
    }
}

【问题讨论】:

    标签: php symfony symfony-forms


    【解决方案1】:

    我不确定这是否是错误,因为此文档: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();
    

    【讨论】:

    • 这里的问题是:模型转换器将规范数据转换为模型数据。在这种情况下,复合形式的规范数据已经是模型对象。 null 只在请求数据中,只能通过事件来寻址。我目前使用事件订阅者做了一个解决方法。我订阅了 PRE_SUBMIT 和 SUBMIT。如果我在 PRE_SUBMIT(请求数据)中看到 null,我会存储它。在提交中,我检查我之前是否看到过null,如果是,我重置数据。这可行,但应谨慎使用,因为它会导致奇怪的行为,例如CollectionType 条目。
    【解决方案2】:

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

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

    目前我使用下面的EventSubscriber来动态地

    1. 查看请求数据是否为空并将其存储以供以后替换数据
    2. 稍后替换数据(因为我需要替换规范数据,而不是请求数据[已经是null])

    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();
    

    【讨论】:

      猜你喜欢
      • 2020-12-06
      • 1970-01-01
      • 2016-09-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-14
      • 2014-11-07
      • 1970-01-01
      相关资源
      最近更新 更多