Symfony2:将表单类型指定为另一个表单的字段类型

Symfony2: Specify form type as a field type of another form

所以我有一个基本形式:

public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
            ->add('firstName')
            ->add('lastName')
            ->add('checklist);
}

其中有一个特定的字段checklist。我创建了一个模型 class,它描述了清单中的所有可能选项

ChecklistModel.php

class ChecklistModel {
    /** @var string **/
    protected $clientSatisfied;

    // ... getters and setters

}

然后,我专门为Checklist创建了一个表单类型。

ChecklistFormType.php

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('clientSatisfied', ChoiceType::class, array(
            'choices' => array(
                'yes' => 'yes',
                'no' => 'no'
            ),
            'choices_as_values' => true,
    ))
}

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => ChecklistModel::class
    ));
}

我想在数据库中将清单存储为一个简单的 JSON 字符串,但我想使用 ChecklistModel 来确保正确提交清单中的所有字段。

我的问题是如何告诉 Symfony 使用 ChecklistFormType 作为基本表单的字段类型 checklist 属性?

我试过

->add('checklist', ChecklistFormType::class);

但我收到以下错误

The form's view data is expected to be an instance of class PT\MyBundle\Models\Invoice\ChecklistModel, but is a(n) string. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) string to an instance of PT\MyBundle\Models\Invoice\ChecklistModel

您必须按照错误消息中的指示指定数据 class,并结合 de/serializer 定义数据转换器。 Symfony documentation

基于我上面的评论,我建议不要在表单类型中进行数据转换(尽管这当然是可能的),而是在使用 json_array type.

的封装模型中进行数据转换

这样只有该模型真正知道数据将如何持久化。

两个相关模型:

src/AppBundle/Entity/FooModel.php

<?php
declare(strict_types=1);

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 */
class FooModel
{
    // other properties (firstName, lastName, ...)

    /**
     * @var array
     *
     * @ORM\Column(type="json_array")
     */
    private $checklist = [];

    /**
     * @param ChecklistModel $checklist
     */
    public function setChecklist(ChecklistModel $checklist)
    {
        $this->checklist = $checklist->toArray();
    }

    /**
     * @return ChecklistModel
     */
    public function getChecklist(): ChecklistModel
    {
        return ChecklistModel::fromArray($this->checklist);
    }
}

ChecklistModel 实施上述方法:

src/AppBundle/Entity/ChecklistModel.php

<?php
declare(strict_types=1);

namespace AppBundle\Entity;

class ChecklistModel
{
    // properties and getters/setters

    /**
     * @param array $data
     *
     * @return ChecklistModel
     */
    public static function fromArray(array $data): ChecklistModel
    {
        $result = new self;

        foreach (get_class_vars(self::class) as $k => $v) {
            if (isset($data[$k])) {
                $result->$k = $data[$k];
            }
        }

        return $result;
    }

    /**
     * @return array
     */
    public function toArray()
    {
        return get_object_vars($this);
    }
}

表格类型:

src/AppBundle/Form/FooFormType.php

<?php
declare(strict_types=1);

namespace AppBundle\Form;

use AppBundle\Entity\FooModel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type as FormType;

class FooFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('firstName', FormType\TextType::class)
            ->add('lastName', FormType\TextType::class)
            ->add('checklist', ChecklistFormType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => FooModel::class,
            'empty_data' => new FooModel(),
        ]);
    }
}

src/AppBundle/Form/ChecklistFormType.php

<?php
declare(strict_types=1);

namespace AppBundle\Form;

use AppBundle\Entity\ChecklistModel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type as FormType;

class ChecklistFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('clientSatisfied', FormType\ChoiceType::class, [
                'choices' => [
                    'yes' => 'yes',
                    'no' => 'no'
                ],
                'choices_as_values' => true,
            ])
            ->add('clientNewCustomer', FormType\ChoiceType::class, [
                'choices' => [
                    'yes' => 'yes',
                    'no' => 'no'
                ],
                'choices_as_values' => true,
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => ChecklistModel::class,
            'empty_data' => new ChecklistModel(),
        ]);
    }
}

用法示例

public function indexAction(Http\Request $request)
{
    $em = $this->getDoctrine()->getManager();

    $data = new Entity\FooModel();

    $form = $this
        ->createForm(FooFormType::class, $data)
        ->handleRequest($request)
    ;

    if ($form->isSubmitted() && $form->isValid()) {
        $em->persist($data);
        $em->flush();
    }

    return $this->render('default/index.html.twig', [
        'form' => $form->createView(),
        'data' => $form->getData(),
    ]);
}

这样 ChecklistFormType 就不需要知道数据是 json 或其他什么。一个ChecklistModel进进出出,不出意外。


也就是说,embeddables 在这里可能是更好的选择。