如何在 Symfony 中创建具有两种形式的页面?
How to create a page with two forms in Symfony?
我想要一个包含两个表单的页面
这两个表单更新同一个实体:帐户。
我做了什么:
- 创建了两个表单(
AccountGeneralDataType.php
& AccountPasswordType.php
)
- 将它们添加到
editAction
方法下的 AccountController.php
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()
]);
}
有什么问题?
- 密码表单的验证不起作用(两个不同的字段不会触发 'fields are different')
- 如果我提交密码字段,$account 设置不正确,导致 Doctrine 错误提示查询缺少参数
我认为我的做法不是很好,但我没有找到任何干净的文档/好的开发人员 post 关于如何让 2 个表单在同一页面上编辑同一实体。
如果您要使用一页和两个表格,它们将在 Request
中分开。
例如,您必须评估提交的表单如下:
if ($request->request->has('editAccountForm')) {
// handle the account form
}
if ($request->request->has('editPasswordForm')) {
// handle the password form
}
有一个针对 Symfony 2 的博客 post covers this。它与 Symfony 3 类似。
您可以使用 twig 在另一个视图中渲染这两个视图。
首先,创建一个新视图并在新控制器中执行操作。
将此控制器路由注册到您的路由文件中。
然后使用它来将您的两个表单视图渲染到新创建的视图中
{{ render(controller('AppBundle:Controller:Action')) }}
这应该可以解决问题...;)
好的,我解决了!
我做了什么:
首先,我在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->isSubmitted()
,我们可以知道表单是否已经提交。
我最大的问题是我的表单的两个类型 类 具有相同的名称(在 getBlockPrefix()
中定义)所以当我提交第二个时,它是第一个抛出错误的.
我想要一个包含两个表单的页面
这两个表单更新同一个实体:帐户。
我做了什么:
- 创建了两个表单(
AccountGeneralDataType.php
&AccountPasswordType.php
) - 将它们添加到
editAction
方法下的AccountController.php
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()
]);
}
有什么问题?
- 密码表单的验证不起作用(两个不同的字段不会触发 'fields are different')
- 如果我提交密码字段,$account 设置不正确,导致 Doctrine 错误提示查询缺少参数
我认为我的做法不是很好,但我没有找到任何干净的文档/好的开发人员 post 关于如何让 2 个表单在同一页面上编辑同一实体。
如果您要使用一页和两个表格,它们将在 Request
中分开。
例如,您必须评估提交的表单如下:
if ($request->request->has('editAccountForm')) {
// handle the account form
}
if ($request->request->has('editPasswordForm')) {
// handle the password form
}
有一个针对 Symfony 2 的博客 post covers this。它与 Symfony 3 类似。
您可以使用 twig 在另一个视图中渲染这两个视图。
首先,创建一个新视图并在新控制器中执行操作。 将此控制器路由注册到您的路由文件中。
然后使用它来将您的两个表单视图渲染到新创建的视图中
{{ render(controller('AppBundle:Controller:Action')) }}
这应该可以解决问题...;)
好的,我解决了!
我做了什么:
首先,我在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->isSubmitted()
,我们可以知道表单是否已经提交。
我最大的问题是我的表单的两个类型 类 具有相同的名称(在 getBlockPrefix()
中定义)所以当我提交第二个时,它是第一个抛出错误的.