【问题标题】:How to map form data in Symfony to an object only when form is valid?仅当表单有效时,如何将 Symfony 中的表单数据映射到对象?
【发布时间】:2018-09-03 14:45:01
【问题描述】:

想象一下 Symfony 中的示例表单:

public function buildForm(FormBuilderInterface $builder)
{
    $builder
        ->add('email', EmailType::class, [
            'constraints' => 
                new NotBlank(),
                new IsUnique(),
            ],
        ])
        ->add('password', PasswordType::class, [
            'constraints' => 
                new NotBlank(),
                new IsStrongEnough(),
            ],
        ])
}

现在,当我提交表单并确保它有效时,我希望 $form->getData() 返回我的 DTO,名为 CreateAccountCommand

final class CreateAccountCommand
{
    private $email;
    private $password;

    public function __construct(string $email, string $password)
    {
        $this->email = $email;
        $this->password = $password;
    }

    public function getEmail(): string
    {
        return $this->email;
    }

    public function getPassword(): string
    {
        return $this->password;
    }
}

示例控制器:

$form = $this->formFactory->create(CreateAccountForm::class);
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
    $this->commandBus->dispatch($form->getData());

    return new JsonResponse([]);
}

我不能通过data_class 直接使用这个类,因为表单显然希望模型具有允许空值的设置器。表单本身运行良好,验证也是如此。

我尝试使用Data mapper 方法,但在验证之前调用了mapFormsToData 方法。

这可能吗?还是我应该将数据作为数组获取并在表单之外创建对象?

【问题讨论】:

  • 在模型中,某些字段(例如电子邮件)可以为空,但您希望它们在表单中是强制性的吗?
  • 我已经更新了我的问题。
  • ->add('email', EmailType::class, array('required' => true))
  • required => true 只是一个 html5 验证(无论如何它都是默认值),不知道你为什么写这个。就像我在问题中所说,验证部分工作正常。
  • 这样,您可以覆盖模型可为空的字段来验证表单。我不得不以相反的方式使用该技巧,我的模型中有不可为空的属性,我不得不使用'required' => false 来强制验证

标签: php symfony symfony-forms symfony4


【解决方案1】:

这是我使用 Symfony 4.1 处理表单的习惯

假设您想要一个简单的添加表单

表格

class CreateAccountForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder)
    {
        //same method than yours
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array('data_class' => 'App\Entity\CreateAccountCommand'));
    }
}

控制器

public function add(Request $request)
{
    $createAccountCommand = new CreateAccountCommand();

    //The object is "injected" to the form, that way, it's mapped when submitted
    $form = $this->get('form.factory')->create(CreateAccountForm::class, $createAccountCommand);

    //Form was submitted
    if ($request->isMethod('POST') && $form->handleRequest($request)->isValid())
    {
        //no need to getData(), the object $createAccountCommand is directly mapped to the form and can be used as is
        $em = $this->getDoctrine()->getManager();
        //persist, convert to json, or whatever
        $em->persist($createAccountCommand);
        $em->flush();

        return ($this->redirectToRoute('someRoute'));
    }

    //form not submitted, or has been submit with errors (isValid() == false)
    return ($this->render('account/add.html.twig',
            array('form' => $form->createView())));
}

【讨论】:

    【解决方案2】:

    有一种方法,但它对于你想做的事情来说太复杂了。为此,您需要使用 Datamapper。

    我知道这不是最好的解决方案,但最简单的解决方案是(在我看来)将设置器添加到您的模型类并允许空值(解决方案1)。另一种解决方案是为表单使用另一个对象,并在提交后构建您的命令(并且您不需要修改您的命令 - SOLUTION2)。

    public function handleForm(Request $request)
    {
        /// SOLUTION 1
        $form = $this->createForm(RegisterFormType::class, new CreateAccountCommand());
        $form->handleRequest($request);
    
        if($form->isSubmitted() && $form->isValid()) {
            $command = $form->getData();
            // do whatever you want
        }
        // ... 
    
        // SOLUTION 2
        $obj = new \stdClass();
        $obj->login = '';
        $obj->password = '';
        $form = $this->createForm(LoginFormType::class, $obj);
        $form->handleRequest($request);
    
        if($form->isSubmitted() && $form->isValid()) {
            $data = $form->getData();
            $command = new CreateAccountCommand($data->login, $data->password);
            // do whatever you want
        }
    }
    

    使用此模型:

    final class CreateAccountCommand
    {
        //// ... 
        /**
         * @param string $email
         */
        public function setEmail(string $email): void
        {
            $this->email = $email;
        }
    
        /**
         * @param string $password
         */
        public function setPassword(string $password): void
        {
            $this->password = $password;
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-27
      • 2020-07-04
      • 1970-01-01
      • 2015-01-01
      相关资源
      最近更新 更多