Symfony 4 通过用户名更改密码 - 电子邮件不能为空
Symfony 4 change password by username - email can not be null
简介
我一直在想办法创建一个由用户名值管理的重设密码表单。
错误
Path Message Invalid value Violation
data.email This value should not be blank. null
ConstraintViolation {#945 ▼
-message: "This value should not be blank."
-messageTemplate: "This value should not be blank."
-parameters: [▶]
-plural: null
-root: Form {#620 ▶}
-propertyPath: "data.email"
-invalidValue: null
-constraint: NotBlank {#477 …}
-code: "c1051bb4-d103-4f74-8988-acbcafc7fdc3"
-cause: null
}
预期结果
用新密码更新我的用户对象。
我的代码
ForgotController.php
我知道这可能不是获取密码的正确方法,但搜索 Symfony 4 忘记密码表单会显示与我的版本无关的 symfony2.4 帖子
<?php
namespace App\Controller\User;
use App\Entity\User;
use App\Form\User\ChangePasswordType;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class ForgotController extends Controller
{
public function forgot(Request $request, UserPasswordEncoderInterface $encoder)
{
$entityManager = $this->getDoctrine()->getManager();
$changePassword = $request->request->get('change_password');
$username = $changePassword['username'];
$password = $changePassword['plainPassword']['first'];
$user = $entityManager->getRepository(User::class)->findBy(['username' => $username]);
$userEntity = new User();
if (!$user) {
$this->addFlash('danger', 'User not found for '. $username);
}
$form = $this->createForm(ChangePasswordType::class, $userEntity);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
try {
$pass = $encoder->encodePassword($userEntity, $password);
$userEntity->setPassword($pass);
$entityManager->flush();
$this->addFlash('success', 'Password Changed!');
} catch (Exception $e) {
$this->addFlash('danger', 'Something went skew-if. Please try again.');
}
return $this->redirectToRoute('login');
}
return $this->render('user/forgot.html.twig', array('form' => $form->createView()));
}
}
ChangePasswordType.php
<?php
namespace App\Form\User;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ChangePasswordType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('username', TextType::class)
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'first_options' => array('label' => 'New Password'),
'second_options' => array('label' => 'Repeat New Password')
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => User::class
));
}
}
forgot.html.twig
{% include 'builder/header.html.twig' %}
<div class="user-container" id="user-content">
{% block body %}
{% include 'builder/notices.html.twig' %}
<div class="user-container">
<i class="fas fa-user-edit fa-5x"></i>
</div>
<hr />
{{ form_start(form) }}
{{ form_row(form.username, { 'attr': {'class': 'form-control'} }) }}
{{ form_row(form.plainPassword.first, { 'attr': {'class': 'form-control'} }) }}
{{ form_row(form.plainPassword.second, { 'attr': {'class': 'form-control'} }) }}
<div class="register-btn-container">
<button class="btn btn-danger" id="return-to-dash-btn" type="button">Cancel!</button>
<button class="btn btn-primary" type="submit">Update!</button>
</div>
{{ form_end(form) }}
{% endblock %}
</div>
{% include 'builder/footer.html.twig' %}
我不确定为什么要提到电子邮件,除非它试图将新用户插入数据库,但它不应该根据我的控制器尝试这样做?我该如何添加由用户名标识的忘记密码表单?
由于您的更改密码表单只需要两个字段,我们将使用数组而不是用户实体。需要稍微调整 ChangePasswordType:
// ChangePasswordType
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
//'data_class' => User::class
));
}
这是一个有效的忘记操作:
public function forgot(
Request $request,
UserPasswordEncoderInterface $encoder,
UserRepository $userRepository)
{
$userInfo = ['username' => null, 'plainPassword' => null];
$form = $this->createForm(ChangePasswordType::class, $userInfo);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$userInfo = $form->getData();
$username = $userInfo['username'];
$plainPassword = $userInfo['plainPassword'];
$user = $userRepository->findOneBy(['username' => $username]);
if ($user === null) {
$this->addFlash('danger', 'Invalid username');
return $this->redirectToRoute('forgot');
}
$password = $encoder->encodePassword($user, $plainPassword);
$user->setPassword($password);
$userRepository->flush();
return $this->redirectToRoute('login');
}
return $this->render('user/forgot.html.twig', array('form' => $form->createView()));
}
UserRepository 被注入,摆脱了所有的学说废话。这里有一个警告,我会回到这里。
我们构建 userInfo 数组并让表单处理完成它。如果不需要的话,我们真的不想直接从请求对象中获取属性。
然后我们更新我们的实际用户实体。请注意使用 findOneBy 而不是 findBy。我们检查以确保用户名有效。如果您真的想要花哨的东西,那么您可以向表单添加验证约束以自动执行此检查。
我摆脱了所有 try/catch 的东西。它只会弄乱你的代码。至此,如果抛出异常,那么它就是真正的异常,可以由默认的异常处理程序处理。
你得到的密码编码器东西刚刚好。
然后我使用 $userRepository->flush() 而不是 $entityManager->flush();开箱即用,存储库上没有刷新方法,因此您需要添加一个:
// UserRepository
public function flush()
{
$this->_em->flush();
}
我个人喜欢只处理存储库而不是实体管理器。但如果需要,您可以返回并注入管理器而不是存储库。你来电。
并且如评论中所述,您确实希望添加一些安全措施以防止用户更改其他用户的密码。
按照下面的方式实施一些东西 - 我已经遗漏了模板和路由等部分。这只是为了帮助你。
表格 1:ForgottenUserType - 仅使用输入 username/email 并提交
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("username", null, array(
"label" => "Email",
"attr" => array(
"class" => "form-control",
"id" => "basic-url",
"placeholder" => "Email address for your account"
),
"constraints" => array(
new Email(array("message" => "Invalid Email"))
)
));
}
形式 2:ChangePasswordFormType - 用户输入并重复新密码。
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'required' => false,
'first_options' => array('label' => 'New password'),
'second_options' => array('label' => 'Confirm new password'),
'invalid_message' => 'The password fields must match.',
))
;
}
控制器:ResetPasswordController - 处理表单 1 的用户查找请求和表单 2 的密码重置请求:
<?php
namespace App\Controller\User;
use App\Entity\User;
use App\Form\User\ChangePasswordType;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class ResetPasswordController extends Controller
{
/**
* Action for when a user has forgotten their password, to request ForgottenUser form
*
* @param Request $request
*/
public function requestAction(Request $request)
{
$tmpUser = new User();
$entityManager = $this->getDoctrine()->getManager();
$form = $this->createForm(ForgottenUserType::class, $tmpUser);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user = $entityManager->getRepository(User::class)->findBy(['username' => $tmpUser->getUsername()]);
if ($user) {
//check and set token
if (null === $user->getConfirmationToken()) {
/** @var $tokenGenerator TokenGeneratorInterface */
$token = md5(uniqid($user->getUsername(), true)); //some unique token (you can create a nicer token generator in standalone class with a service)
$user->setConfirmationToken($token);
$user->setPasswordRequestedAt(new \DateTime());
$em->persist($user);
$em->flush();)
$this->addFlash('Info', 'If user is found, you will receive an email with further instructions.');
//send email using swiftmailer & include url with token
}
} else {
//return to requestAction.
}
}
//request template contains the ForgottenUserType form
return $this->render(":path/to/template:request.html.twig", array(
"forgotten_form" => $form->createView()
));
}
/**
* Reset user password.
*
* @param Request $request
* @param $token
*/
public function resetAction(Request $request, $token)
{
$entityManager = $this->getDoctrine()->getManager();
$user = $entityManager->getRepository(User::class)->findBy(['confirmationToken' => $token]);
if (null === $user) {
return new RedirectResponse($this->generateUrl('resetting_request')); //to requestAction above. / create route
}
$form = $this->createForm(ChangePasswordFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user->SetConfirmationToken(null);
$user->setPasswordRequestedAt(null);
$entityManager->persist($user);
$entityManager->flush()
$this->addFlash("success", "Your password has been reset, log in now.");
$url = $this->generateUrl('app.login'); //route to login page
$response = new RedirectResponse($url);
return $response;
}
//reset template contains the ChangePasswordFormType form
return $this->render(':path/to/forgottenpasswordtemplate:reset.html.twig', array(
'token' => $token,
'form' => $form->createView(),
));
}
}
简介
我一直在想办法创建一个由用户名值管理的重设密码表单。
错误
Path Message Invalid value Violation
data.email This value should not be blank. null
ConstraintViolation {#945 ▼
-message: "This value should not be blank."
-messageTemplate: "This value should not be blank."
-parameters: [▶]
-plural: null
-root: Form {#620 ▶}
-propertyPath: "data.email"
-invalidValue: null
-constraint: NotBlank {#477 …}
-code: "c1051bb4-d103-4f74-8988-acbcafc7fdc3"
-cause: null
}
预期结果
用新密码更新我的用户对象。
我的代码
ForgotController.php
我知道这可能不是获取密码的正确方法,但搜索 Symfony 4 忘记密码表单会显示与我的版本无关的 symfony2.4 帖子
<?php
namespace App\Controller\User;
use App\Entity\User;
use App\Form\User\ChangePasswordType;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class ForgotController extends Controller
{
public function forgot(Request $request, UserPasswordEncoderInterface $encoder)
{
$entityManager = $this->getDoctrine()->getManager();
$changePassword = $request->request->get('change_password');
$username = $changePassword['username'];
$password = $changePassword['plainPassword']['first'];
$user = $entityManager->getRepository(User::class)->findBy(['username' => $username]);
$userEntity = new User();
if (!$user) {
$this->addFlash('danger', 'User not found for '. $username);
}
$form = $this->createForm(ChangePasswordType::class, $userEntity);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
try {
$pass = $encoder->encodePassword($userEntity, $password);
$userEntity->setPassword($pass);
$entityManager->flush();
$this->addFlash('success', 'Password Changed!');
} catch (Exception $e) {
$this->addFlash('danger', 'Something went skew-if. Please try again.');
}
return $this->redirectToRoute('login');
}
return $this->render('user/forgot.html.twig', array('form' => $form->createView()));
}
}
ChangePasswordType.php
<?php
namespace App\Form\User;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ChangePasswordType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('username', TextType::class)
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'first_options' => array('label' => 'New Password'),
'second_options' => array('label' => 'Repeat New Password')
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => User::class
));
}
}
forgot.html.twig
{% include 'builder/header.html.twig' %}
<div class="user-container" id="user-content">
{% block body %}
{% include 'builder/notices.html.twig' %}
<div class="user-container">
<i class="fas fa-user-edit fa-5x"></i>
</div>
<hr />
{{ form_start(form) }}
{{ form_row(form.username, { 'attr': {'class': 'form-control'} }) }}
{{ form_row(form.plainPassword.first, { 'attr': {'class': 'form-control'} }) }}
{{ form_row(form.plainPassword.second, { 'attr': {'class': 'form-control'} }) }}
<div class="register-btn-container">
<button class="btn btn-danger" id="return-to-dash-btn" type="button">Cancel!</button>
<button class="btn btn-primary" type="submit">Update!</button>
</div>
{{ form_end(form) }}
{% endblock %}
</div>
{% include 'builder/footer.html.twig' %}
我不确定为什么要提到电子邮件,除非它试图将新用户插入数据库,但它不应该根据我的控制器尝试这样做?我该如何添加由用户名标识的忘记密码表单?
由于您的更改密码表单只需要两个字段,我们将使用数组而不是用户实体。需要稍微调整 ChangePasswordType:
// ChangePasswordType
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
//'data_class' => User::class
));
}
这是一个有效的忘记操作:
public function forgot(
Request $request,
UserPasswordEncoderInterface $encoder,
UserRepository $userRepository)
{
$userInfo = ['username' => null, 'plainPassword' => null];
$form = $this->createForm(ChangePasswordType::class, $userInfo);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$userInfo = $form->getData();
$username = $userInfo['username'];
$plainPassword = $userInfo['plainPassword'];
$user = $userRepository->findOneBy(['username' => $username]);
if ($user === null) {
$this->addFlash('danger', 'Invalid username');
return $this->redirectToRoute('forgot');
}
$password = $encoder->encodePassword($user, $plainPassword);
$user->setPassword($password);
$userRepository->flush();
return $this->redirectToRoute('login');
}
return $this->render('user/forgot.html.twig', array('form' => $form->createView()));
}
UserRepository 被注入,摆脱了所有的学说废话。这里有一个警告,我会回到这里。
我们构建 userInfo 数组并让表单处理完成它。如果不需要的话,我们真的不想直接从请求对象中获取属性。
然后我们更新我们的实际用户实体。请注意使用 findOneBy 而不是 findBy。我们检查以确保用户名有效。如果您真的想要花哨的东西,那么您可以向表单添加验证约束以自动执行此检查。
我摆脱了所有 try/catch 的东西。它只会弄乱你的代码。至此,如果抛出异常,那么它就是真正的异常,可以由默认的异常处理程序处理。
你得到的密码编码器东西刚刚好。
然后我使用 $userRepository->flush() 而不是 $entityManager->flush();开箱即用,存储库上没有刷新方法,因此您需要添加一个:
// UserRepository
public function flush()
{
$this->_em->flush();
}
我个人喜欢只处理存储库而不是实体管理器。但如果需要,您可以返回并注入管理器而不是存储库。你来电。
并且如评论中所述,您确实希望添加一些安全措施以防止用户更改其他用户的密码。
按照下面的方式实施一些东西 - 我已经遗漏了模板和路由等部分。这只是为了帮助你。
表格 1:ForgottenUserType - 仅使用输入 username/email 并提交
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("username", null, array(
"label" => "Email",
"attr" => array(
"class" => "form-control",
"id" => "basic-url",
"placeholder" => "Email address for your account"
),
"constraints" => array(
new Email(array("message" => "Invalid Email"))
)
));
}
形式 2:ChangePasswordFormType - 用户输入并重复新密码。
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'required' => false,
'first_options' => array('label' => 'New password'),
'second_options' => array('label' => 'Confirm new password'),
'invalid_message' => 'The password fields must match.',
))
;
}
控制器:ResetPasswordController - 处理表单 1 的用户查找请求和表单 2 的密码重置请求:
<?php
namespace App\Controller\User;
use App\Entity\User;
use App\Form\User\ChangePasswordType;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class ResetPasswordController extends Controller
{
/**
* Action for when a user has forgotten their password, to request ForgottenUser form
*
* @param Request $request
*/
public function requestAction(Request $request)
{
$tmpUser = new User();
$entityManager = $this->getDoctrine()->getManager();
$form = $this->createForm(ForgottenUserType::class, $tmpUser);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user = $entityManager->getRepository(User::class)->findBy(['username' => $tmpUser->getUsername()]);
if ($user) {
//check and set token
if (null === $user->getConfirmationToken()) {
/** @var $tokenGenerator TokenGeneratorInterface */
$token = md5(uniqid($user->getUsername(), true)); //some unique token (you can create a nicer token generator in standalone class with a service)
$user->setConfirmationToken($token);
$user->setPasswordRequestedAt(new \DateTime());
$em->persist($user);
$em->flush();)
$this->addFlash('Info', 'If user is found, you will receive an email with further instructions.');
//send email using swiftmailer & include url with token
}
} else {
//return to requestAction.
}
}
//request template contains the ForgottenUserType form
return $this->render(":path/to/template:request.html.twig", array(
"forgotten_form" => $form->createView()
));
}
/**
* Reset user password.
*
* @param Request $request
* @param $token
*/
public function resetAction(Request $request, $token)
{
$entityManager = $this->getDoctrine()->getManager();
$user = $entityManager->getRepository(User::class)->findBy(['confirmationToken' => $token]);
if (null === $user) {
return new RedirectResponse($this->generateUrl('resetting_request')); //to requestAction above. / create route
}
$form = $this->createForm(ChangePasswordFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user->SetConfirmationToken(null);
$user->setPasswordRequestedAt(null);
$entityManager->persist($user);
$entityManager->flush()
$this->addFlash("success", "Your password has been reset, log in now.");
$url = $this->generateUrl('app.login'); //route to login page
$response = new RedirectResponse($url);
return $response;
}
//reset template contains the ChangePasswordFormType form
return $this->render(':path/to/forgottenpasswordtemplate:reset.html.twig', array(
'token' => $token,
'form' => $form->createView(),
));
}
}