Проблема: У меня возникла проблема, когда, если пользователь отправляет электронное письмо на мою уже использованную форму, он получает сообщение об ошибке (как и предполагалось). После получения ошибки, если страница обновляется или они переходят на другую страницу на сайте, на котором пользователь вышел из системы, я думаю, что пользователь уже вышел из системы после того, как он получил форму с ошибкой, но страница по-прежнему отображается как пользователь вошел в систему.
Пользовательский объект не имеет поля имени пользователя и использует адрес электронной почты в качестве визуального идентификатора.
Сводка : после сбоя формы пользователя uniqueEntity validation user выходит из системы при следующем запросе,
Вопрос : Можно ли как-то предотвратить это поведение?
ОБНОВЛЕНИЕ: похоже вызывает сеттеры объекта пользователя, когда вы пытаетесь обновить электронную почту независимо от того, является ли она действительной или нет. пользователь, который хранится в сеансе, видоизменяется, а затем после следующего запроса он не совпадает с пользователем в базе данных и нуждается в повторной аутентификации. Это ошибка, которая возникает только тогда, когда объект текущего пользователя помещается в форму. вопрос остается прежним.
Здесь я включу весь код, необходимый для воспроизведения этой проблемы.
Класс пользователя:
/**
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
*
* @UniqueEntity(
* fields={"email"},
* message="general.unique_email"
* )
*/
class User implements UserInterface
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=180, unique=true)
* @Assert\NotBlank(message="general.not_blank")
* @Assert\Email
*/
private $email;
/**
* @ORM\Column(type="json")
*/
private $roles = [];
/**
* @var string The hashed password
* @ORM\Column(type="string")
*/
private $password;
/**
* @ORM\Column(type="date")
* @Assert\NotBlank(message="general.not_blank")
* @Assert\LessThan(
* value="-16 years",
* message="general.min_age"
* )
*/
private $birthDate;
/**
* @ORM\Column(type="string", length=255)
* @Assert\NotBlank(message="general.not_blank")
* @Assert\Length(
* min="10",
* minMessage="general.characters_min",
* max="15",
* maxMessage="general.characters_max",
* )
*/
private $phone;
/**
* @ORM\Column(type="string", length=255)
* @Assert\NotBlank(message="general.not_blank")
* @Assert\Length(
* min="2",
* minMessage="general.characters_min",
* max="30",
* maxMessage="general.characters_max",
* )
*/
private $firstName;
/**
* @ORM\Column(type="string", length=255, nullable=true)
* @Assert\Length(
* min="2",
* minMessage="general.characters_min",
* max="20",
* maxMessage="general.characters_max",
* )
*/
private $prepositions;
/**
* @ORM\Column(type="string", length=255)
* @Assert\NotBlank(message="general.not_blank")
* @Assert\Length(
* min="2",
* minMessage="general.characters_min",
* max="30",
* maxMessage="general.characters_max",
* )
*/
private $lastName;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
private $gender;
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(?string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUsername(): string
{
return (string) $this->email;
}
/**
* @see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(?array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* @see UserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(?string $password): self
{
$this->password = $password;
return $this;
}
/**
* @see UserInterface
*/
public function getSalt()
{
// not needed when using the "bcrypt" algorithm in security.yaml
}
/**
* @see UserInterface
*/
public function eraseCredentials(): void
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function getBirthDate(): ?DateTimeInterface
{
return $this->birthDate;
}
public function setBirthDate(?DateTimeInterface $birthDate): self
{
$this->birthDate = $birthDate;
return $this;
}
public function getPhone(): ?string
{
return $this->phone;
}
public function setPhone(?string $phone): self
{
$this->phone = $phone;
return $this;
}
public function getFirstName(): ?string
{
return $this->firstName;
}
public function setFirstName(?string $firstName): self
{
$this->firstName = $firstName;
return $this;
}
public function getPrepositions(): ?string
{
return $this->prepositions;
}
public function setPrepositions(?string $prepositions): self
{
$this->prepositions = $prepositions;
return $this;
}
public function getLastName(): ?string
{
return $this->lastName;
}
public function setLastName(?string $lastName): self
{
$this->lastName = $lastName;
return $this;
}
public function getGender(): ?bool
{
return $this->gender;
}
public function setGender(?bool $gender): self
{
$this->gender = $gender;
return $this;
}
}
Класс формы:
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', EmailType::class, [
'label' => 'user.email',
])
->add('firstName', TextType::class, [
'label' => 'user.first_name',
])
->add('prepositions', TextType::class, [
'label' => 'user.prepositions',
'required' => false,
])
->add('lastName', TextType::class, [
'label' => 'user.last_name'
])
->add('phone', TextType::class, [
'label' => 'user.phone',
])
->add('birthDate', BirthdayType::class, [
'label' => 'user.birth_date',
'widget' => 'single_text',
'placeholder' => [
'day' => 'general.day',
'month' => 'general.mon th',
'year' => 'general.year',
]
])
->add('gender', ChoiceType::class, [
'label' => 'user.gender.label',
'choices' => [
'user.gender.male' => true,
'user.gender.female' => false,
'user.gender.other' => null,
]
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
'translation_domain' => 'form',
]);
}
}
Контроллер:
/**
* @IsGranted("ROLE_USER")
* @Route("/account", name="account")
*/
public function account(Request $request, UserPasswordEncoderInterface $passwordEncoder, EntityManagerInterface $em): Response
{
/** @var User $user */
$user = $this->getUser();
$userForm = $this->createForm(UserType::class, $user);
$userForm->handleRequest($request);
if ($userForm->isSubmitted() && $userForm->isValid()) {
$this->addFlash('success', 'applied user data');
$em->flush();
return $this->redirectToRoute('app_security_account');
}
$passwordForm = $this->createForm(ChangePasswordType::class);
$passwordForm->handleRequest($request);
if ($passwordForm->isSubmitted() && $passwordForm->isValid()) {
/** @var ChangePassword $changePassword */
$changePassword = $passwordForm->getData();
$this->getUser()->setPassword($passwordEncoder->encodePassword($this->getUser(), $changePassword->getNewPassword()));
$em->flush();
$this->addFlash('success', 'changed password');
return $this->redirectToRoute('app_security_account');
}
return $this->render('page/security/account.html.twig', [
'title' => 'My Site | Account',
'userForm' => $userForm->createView(),
'passwordForm' => $passwordForm->createView(),
]);
}
Шаблон веточки:
{% extends 'content_base.html.twig' %}
{% block content_container %}
<h1>{{ 'user.header'|trans([], 'form') }}</h1>
{{ form_start(userForm, {"novalidate": "novalidate"}) }}
{{ form_widget(userForm) }}
<button class="btn btn-warning btn-block" type="submit">Submit</button>
{{ form_end(userForm) }}
<h1>{{ 'password.header'|trans([], 'form') }}</h1>
{{ form_start(passwordForm, {"novalidate": "novalidate"}) }}
{{ form_widget(passwordForm) }}
<button class="btn btn-warning btn-block" type="submit">Submit</button>
{{ form_end(passwordForm) }}
{% endblock %}
Security.yaml:
security:
encoders:
App\Entity\User:
algorithm: auto
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: email
# used to reload user from session & other features (e.g. switch_user)
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: lazy
provider: app_user_provider
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: app_security_logout
# where to redirect after logout
target: app_security_login
remember_me:
secret: '%kernel.secret%'
lifetime: 604800 # 1 week in seconds
path: /