php 如何将Symfony中的表单数据仅在表单有效时Map到对象?

1szpjjfi  于 2023-09-29  发布在  PHP
关注(0)|答案(3)|浏览(113)

想象一下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()返回名为CreateAccountCommand的DTO:

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来使用这个类,因为表单显然期望模型具有允许空值的setter。表单本身工作得很好,验证也是如此。
我尝试使用Data mapper方法,但在验证之前调用了mapFormsToData方法。
这可能吗?或者我应该以数组的形式获取数据并在表单外创建对象?

pnwntuvh

pnwntuvh1#

下面是我使用Symfony 4.1处理表单的方法
让我们假设您想要一个简单的add表单
形式

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

ctehm74n2#

有一种方法,但它对于你想做的事情来说太复杂了。您需要使用Datamapper。
我知道这不是最好的解决方案,但最简单的解决方案是(在我看来)在模型类中添加setter并允许空值(SOLUTION 1)。另一种解决方案是为表单使用另一个对象,并在提交后构建您的命令(并且您不需要修改您的命令-SOLUTION 2)。

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;
    }

}
ws51t4hk

ws51t4hk3#

有点晚了,但也许这个答案会帮助处于同样情况的人。
在我看来,Command(CQ(R)S的意思)是一个简单的DTO,它接受无效数据。因此,您应该直接在其上应用约束,而不是应用到表单。
在你的例子中:

final class CreateAccountCommand
{
    public function __construct(
        #[Assert\NotBlank]
        #[Assert\IsUnique]
        public string $email = '',

        #[Assert\NotBlank]
        #[Assert\IsStrongEnough]
        public string $password = '',
    ) {
    }
}

通过这种方式,您可以受益于所有Symfony的好处,并在您的命令处理程序中强制您的域模型在验证通过后有效。

相关问题