【问题标题】:How to create a page with two forms in Symfony?如何在 Symfony 中创建具有两种表单的页面?
【发布时间】:2018-01-11 12:41:54
【问题描述】:

我想要一个包含两个表单的页面

这两个表单更新同一个实体:帐户。

我做了什么:

  • 创建了两个表单(AccountGeneralDataType.php & AccountPasswordType.php
  • 将它们添加到AccountController.php 下的editAction 方法

AccountGeneralDataType.php

<?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

class AccountGeneralDataType extends AbstractType
{

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        /**
         * todo: Missing avatar upload
         */
        $builder
            ->add('username', TextType::class, [
                'label' => 'Username'
            ])
            ->add('email', EmailType::class, [
                'label' => 'Email'
            ])
            ->add('submit', SubmitType::class, [
                'label' => 'Save changes',
                'attr' => [
                    'class' => 'btn btn-outline-primary float-right'
                ]
            ]);
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'AppBundle\Entity\Account'
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_account';
    }

}

AccountPasswordType.php

<?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Validator\Constraints\NotBlank;

class AccountPasswordType extends AbstractType
{

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('password', PasswordType::class, [
                'label' => 'Old password',
            ])
            ->add('new-password', RepeatedType::class, [
                'label' => 'New password',
                'type' => PasswordType::class,
                'invalid_message' => 'The new password fields must match.',
                'options' => ['attr' => ['class' => 'password-field']],
                'required' => true,
                'first_options' => [
                    'label' => 'New password'
                ],
                'second_options' => [
                    'label' => 'Repeat password'
                ],
                'constraints' => [
                    new NotBlank(),
                ],
            ])
            ->add('submit', SubmitType::class, [
                'label' => 'Update password',
                'attr' => [
                    'class' => 'btn btn-outline-primary float-right'
                ]
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
//            'data_class' => 'AppBundle\Entity\Account'
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_account';
    }

}

AccountController.php

public function editAction(Request $request, Account $account)
{
    $originalAccount = new Account();
    $originalAccount->setEmail($account->getEmail());
    $originalAccount->setUsername($account->getUsername());
    $originalAccount->setAvatar($account->getAvatar());

    /**
     * Form to edit general information
     */
    $editAccountForm = $this->createForm('AppBundle\Form\AccountGeneralDataType', $account);
    $editAccountForm->handleRequest($request);

    /**
     * Form to edit password
     */
    $editPasswordForm = $this->createForm('AppBundle\Form\AccountPasswordType', []);
    $editPasswordForm->handleRequest($request);

    /**
     * Form to delete the account
     */
    $deleteForm = $this->createDeleteForm($account);

    if ($editAccountForm->isSubmitted() && $editAccountForm->isValid()) {
        $this->getDoctrine()->getManager()->flush();

        return $this->redirectToRoute('account_edit', array('username' => $account->getUsername()));
    } else if ($editPasswordForm->isSubmitted() && $editPasswordForm->isValid()) {
        $account = $originalAccount;

        $this->getDoctrine()->getManager()->flush();
        return $this->redirectToRoute('account_edit', [
                'username' => $originalAccount->getUsername()
        ]);
    }

有什么问题?

  • 密码表单的验证不起作用(两个不同的字段不会触发“字段不同”)
  • 如果我提交密码字段,$account 未正确设置,导致 Doctrine 错误提示查询缺少参数

我认为我的做法不是很好,但我没有找到任何关于如何让 2 个表单在同一页面上编辑同一实体的干净文档/好的开发帖子。

【问题讨论】:

  • 为什么要求在同一页面上有两个?如果一个(或两个)表单共享一个“提交”按钮并编辑同一个实体,这将非常简单。
  • 这是一个用户体验选择。就像 GitHub 页面在 Profile 版本页面中使用多个表单一样,例如。
  • 快速阅读后,我会说它应该可以工作,我在我的项目中使用了非常相似的东西(除了 FormTypes 没有链接到实体)。不幸的是,我现在没有时间详细检查您的代码,但我今晚会尝试看看。与此同时,也许我在这里的旧答案可以帮助你(我知道我不应该写链接,但无论如何......):stackoverflow.com/questions/47893964/…
  • 谢谢@Nicolas,它真的帮助了我!

标签: php forms symfony symfony-forms symfony3.x


【解决方案1】:

好的,我解决了!

我做了什么:

首先,我在project/src/AppBundle/Form/中创建了两个表单:

AccountGeneralDataType.php

<?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

class AccountGeneralDataType extends AbstractType
{

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        /**
         * todo: Missing avatar upload
         */
        $builder
            ->add('username', TextType::class, [
                'label' => 'Username',
                'required' => true,
            ])
            ->add('email', EmailType::class, [
                'label' => 'Email',
                'required' => true,
            ])
            ->add('submit', SubmitType::class, [
                'label' => 'Save changes',
                'attr' => [
                    'class' => 'btn btn-outline-primary float-right',
                ]
            ]);
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'AppBundle\Entity\Account',
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_general_data_account';
    }

}

不要忘记在getBlockPrefix() 中设置唯一标签!

AccountPasswordType.php

<?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Validator\Constraints\NotBlank;

class AccountPasswordType extends AbstractType
{

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('old-password', PasswordType::class, [
                'label' => 'Old password',
                'mapped' => false,
            ])
            ->add('new-password', RepeatedType::class, [
                'label' => 'New password',
                'mapped' => false,
                'type' => PasswordType::class,
                'invalid_message' => 'The new password fields must match.',
                'options' => ['attr' => ['class' => 'password-field']],
                'required' => true,
                'first_options' => [
                    'label' => 'New password'
                ],
                'second_options' => [
                    'label' => 'Repeat password'
                ],
                'constraints' => [
                    new NotBlank(),
                ],
            ])
            ->add('submit', SubmitType::class, [
                'label' => 'Update password',
                'attr' => [
                    'class' => 'btn btn-outline-primary float-right'
                ]
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'allow_extra_fields' => true,
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_change_password';
    }

}

不要忘记在getBlockPrefix() 中设置唯一标签!

AccountPasswordType 不使用data_class(在configureOptions() 方法中设置),因为我不会用它自动填充对象。

其次,我在控制器中调用它们:

AccountController.php

<?php

namespace AppBundle\Controller;

use AppBundle\Entity\Account;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Form\FormError;

/**
 * Account controller.
 *
 * @Route("account")
 */
class AccountController extends Controller
{
    /**
     * Displays forms to edit an existing account entity.
     *
     * @Route("/edit/{username}", name="account_edit")
     * @Method({"GET", "POST"})
     */
    public function editAction(Request $request, Account $account)
    {
        /**
         * Form to edit general information
         */
        $editAccountGeneralInformationForm = $this->createForm('AppBundle\Form\AccountGeneralDataType', $account);
        $editAccountGeneralInformationForm->handleRequest($request);

        /**
         * Form to edit password
         */
        $editAccountPasswordForm = $this->createForm('AppBundle\Form\AccountPasswordType', []);
        $editAccountPasswordForm->handleRequest($request);

        // Handle the account form
        if ($editAccountGeneralInformationForm->isSubmitted() && !$editAccountGeneralInformationForm->isValid())
        {
            $session->getFlashBag()->add('danger', 'Your iinformation have not been updated, please retry.');
        }

        if ($editAccountGeneralInformationForm->isSubmitted() && $editAccountGeneralInformationForm->isValid())
        {
            $this->getDoctrine()->getManager()->flush();

            $session->getFlashBag()->add('success', 'Your information have been updated.');
        }


        // Handle the password form
        if ($editAccountPasswordForm->isSubmitted() && !$editAccountPasswordForm->isValid())
        {
            $session->getFlashBag()->add('danger', 'Your password have not been updated, please retry.');
        }

        if ($editAccountPasswordForm->isSubmitted() && $editAccountPasswordForm->isValid())
        {
            $oldUserPassword = $account->getPassword();
            $oldPasswordSubmitted = $editAccountPasswordForm['old-password']->getData();
            $newPasswordSubmitted = $editAccountPasswordForm['new-password']->getData();

            if (password_verify($oldPasswordSubmitted, $oldUserPassword))
            {
                $newPassword = password_hash($newPasswordSubmitted, PASSWORD_BCRYPT, ['cost' => 15]);
                $account->setPassword($newPassword);
                $this->getDoctrine()->getManager()->flush();

                $session->getFlashBag()->add('success', 'Your password have been updated.');
            }
            else
            {
                $editAccountPasswordForm->get('old-password')->addError(new FormError('Incorrect password.'));
                $session->getFlashBag()->add('danger', 'Your password have not been updated, please retry.');
            }
        }

        return $this->render('account/edit.html.twig', [
                'account' => $account,
                'edit_form' => $editAccountGeneralInformationForm->createView(),
                'edit_password_form' => $editAccountPasswordForm->createView(),
        ]);
    }
}

使用$foobarForm-&gt;isSubmitted()可以知道表单是否已经提交。

我最大的问题是我的表单的两个 Type 类具有相同的名称(在 getBlockPrefix() 中定义)所以当我提交第二个时,它是第一个引发错误的。

【讨论】:

  • 谢谢 - 在提交单独的表单(JS 分隔的 div 切换)时遇到了困难,因为提交两者都触发了表单验证。 getBlockPrefix 解决了这个问题!
  • 控制器中的提示顺便说一句,我这样做了if ($formA-&gt;isSubmitted() || $formB-&gt;isSubmitted()) { 然后你可以这样做$submittedForm = $formA-&gt;isSubmitted() ? $formA : $formB;
【解决方案2】:

您可以使用 twig 在另一个视图中渲染两个视图。

首先,创建一个新视图并在新控制器中执行它。 将此控制器路由注册到您的路由文件中。

然后用它把你的两个表单视图渲染到新创建的视图中

{{ render(controller('AppBundle:Controller:Action')) }}

这应该可以解决问题... ;)

【讨论】:

    【解决方案3】:

    如果您要使用一个页面和两个表单,它们将在 Request 中分开。

    例如,您必须评估提交的表单如下:

    if ($request->request->has('editAccountForm')) {
        // handle the account form 
    }
    
    if ($request->request->has('editPasswordForm')) {
        // handle the password form  
    }
    

    有一篇关于 Symfony 2 的博客文章 covers this。 Symfony 3 也是如此。

    【讨论】:

      猜你喜欢
      • 2019-08-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多