Symfony 3, заполнение токена и обновление пользователя

репозиторий с проблемой

У меня есть форма для пользователя с полем электронной почты:

->add('email', EmailType::class, [
                'constraints' => [
                    new NotBlank(),
                    new Email([
                        'checkMX' => true,
                    ])
                ],
                'required' => true
            ])

когда я редактирую электронную почту на что-то вроде [email protected] и отправляю форму, она показывает мне ошибку "Это значение не является допустимым адресом электронной почты." Это нормально, но после этого symfony заполняет неправильный адрес электронной почты в токене и когда я я перехожу на любую другую страницу или просто перезагружаю страницу, я получаю это:

ВНИМАНИЕ безопасность Имя пользователя не может быть найдено в выбранном поставщике пользователей.

Я думаю, вопрос в следующем: почему symfony заполняет неправильный адрес электронной почты, который не прошел проверку, в токен, и как я могу это предотвратить?

контроллер:

public function meSettingsAction(Request $request)
    {

        $user = $this->getUser();
        $userUnSubscribed = $this->getDoctrine()->getRepository('AppBundle:UserUnsubs')->findOneBy(
            [
                'email' => $user->getEmail(),
            ]
        );

        $form = $this->createForm(UserSettingsType::class, $user);
        $form->get('subscribed')->setData(!(bool)$userUnSubscribed);

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            /**
             * @var $user User
             */
            $user = $form->getData();

            /** @var UploadedFile $avatar */
            $avatar = $request->files->get('user_settings')['photo'];

            $em = $this->getDoctrine()->getManager();

            if ($avatar) {
                $avatar_content = file_get_contents($avatar->getRealPath());
                $avatarName = uniqid().'.jpg';
                $oldAvatar = $user->getPhoto();
                $user
                    ->setState(User::PHOTO_STATE_UNCHECKED)
                    ->setPhoto($avatarName);
                $gearmanClient = $this->get('gearman.client');
                $gearmanClient->doBackgroundDependsOnEnv(
                    'avatar_content_upload',
                    serialize(['content' => $avatar_content, 'avatarName' => $avatarName, 'oldAvatar' => $oldAvatar])
                );
            }

            $subscribed = $form->get('subscribed')->getData();
            if ((bool)$userUnSubscribed && $subscribed) {
                $em->remove($userUnSubscribed);
            } elseif (!(bool)$userUnSubscribed && !$subscribed) {
                $userUnSubscribed = new UserUnsubs();
                $userUnSubscribed->setEmail($form->get('email')->getData())->setTs(time());
                $em->persist($userUnSubscribed);
            }
            $user->setLastTs(time());
            $em = $this->getDoctrine()->getManager();
            $em->persist($user);
            $em->flush();

            $this->get('user.manager')->refresh($user);

            return $this->redirectToRoute('me');
        }

        return $this->render(
            ':user:settings.html.twig',
            [
                'form' => $form->createView(),
            ]
        );
    }

UPD: все работает нормально, если я изменю в OAuthProvider:

/**
 * @param \Symfony\Component\Security\Core\User\UserInterface $user
 *
 * @return \Symfony\Component\Security\Core\User\UserInterface
 */
public function refreshUser(UserInterface $user)
{
    return $this->loadUserByUsername($user->getName());
}

to:

/**
 * @param \Symfony\Component\Security\Core\User\UserInterface $user
 *
 * @return \Symfony\Component\Security\Core\User\UserInterface
 */
public function refreshUser(UserInterface $user)
{
    return $this->userManager($user->getId());
}

но это, кажется, грязный взлом.

Спасибо.


person kRicha    schedule 08.06.2017    source источник
comment
можешь показать свой контроллер?   -  person Alessandro Minoccheri    schedule 08.06.2017
comment
@AlessandroMinoccheri Я добавил код контроллера к вопросу.   -  person kRicha    schedule 08.06.2017
comment
почему refreshUser() вообще вызывается, если электронная почта неверна? Пожалуйста, добавьте дополнительную информацию о вашей конфигурации serurity.yml.   -  person lordrhodos    schedule 10.06.2017
comment
@lordrhodos Я не знаю, почему он звонит. А проблема в другом, перечитайте вопрос.   -  person kRicha    schedule 10.06.2017
comment
С какой организацией связан ваш UserSettingsType ? Кто ваши пользователи? Простая реализация UserInterface или что-то из внешнего пакета, такого как FOSUserBundle? Как уже просили, можем ли мы получить ваш security.yml ? Нам действительно нужно больше информации, чтобы иметь возможность воспроизвести проблему.   -  person Growiel    schedule 12.06.2017
comment
@Growiel UserSettingsType с использованием сущности пользователя — простая реализация UserInterface, EquatableInterface. Я не использую FOSUserBundle. Какая информация вам нужна из моего security.yml?   -  person kRicha    schedule 14.06.2017
comment
@kRicha все из security.yml, но в основном о провайдерах пользователей, средствах проверки пользователей и обо всем, что может быть причиной вашей проблемы. Я не понимаю, как вы можете назначить вознаграждение за вопрос, не предоставляя всей необходимой информации, хотя запрашивается несколько раз и чем ожидаете получить ответ.   -  person lordrhodos    schedule 16.06.2017
comment
@lordrhodos я добавил пример кода для воспроизведения проблемы. Посмотрите на вопрос, теперь вы можете попробовать.   -  person kRicha    schedule 16.06.2017


Ответы (2)


Это сложный вопрос, благодаря репозиторию было легче изолировать проблему. Вы привязываете пользовательский объект из токена аутентификации к методу createForm(). После

$form->handleRequest($request)

вызов электронной почты от объекта пользователя токена обновляется.

Сначала я решил решить эту проблему путем реализации EquatableInterface.html. в объекте User, но это не сработало, так как для сравниваемого объекта уже был задан неправильный адрес электронной почты.

Также может быть полезно реализовать интерфейс EquatableInterface, который определяет метод для проверки, равен ли пользователь текущему пользователю. Для этого интерфейса требуется метод isEqualTo().)

Затем я подумал о принудительной перезагрузке пользователя из базы данных и сбросе токена безопасности, но мне пришло в голову, что может быть достаточно просто обновить текущий объект пользователя из базы данных в случае сбоя формы:

$this->get('doctrine')->getManager()->refresh($this->getUser());`

В вашем контроллере это решит вашу проблему.

/**
 * @Route("/edit_me", name="edit")
 * @Security("has_role('ROLE_USER')")
 */
public function editMyselfAction(Request $request) {
    $form = $this->createForm(User::class, $this->getUser());

    if ($request->isMethod(Request::METHOD_POST)) {
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $user = $form->getData();
            $em = $this->getDoctrine()->getManager();
            $em->persist($user);
            $em->flush();
        } else {
            $this->get('doctrine')->getManager()->refresh($this->getUser());
        }
    }

    return $this->render(':security:edit.html.twig',['form' => $form->createView()]);
}

Альтернативное решение

ошибка в репозитории Symfony позволила получить ценные сведения о Избегание объектов в формах и Разделение вашего пользователя безопасности, что обеспечивает более сложный подход к решению вашей проблемы.

person lordrhodos    schedule 17.06.2017
comment
спасибо за внимание, но это грязный хак :D я уже отправил тикеты в репозиторий symfony/symfony на github и предоставил им свой репозиторий. Это ненормальное поведение заполнения токена. У меня уже есть еще один грязный хак, описанный в моем ответе. Спасибо еще раз) - person kRicha; 17.06.2017
comment
Ну, если честно, я считаю это не грязным хаком, а необходимым решением, потому что то, как вы привязываете $user из объекта токена к форме. Это ожидаемое поведение. Если вы хотите избежать этого, вы должны создать новый пустой пользовательский объект, инициализировать его необходимыми данными от текущего пользователя, вошедшего в систему, и привязать его к форме. Если форма действительна, вы вручную сопоставляете отправленный пользовательский объект с пользовательским объектом из токена. - person lordrhodos; 17.06.2017
comment
просто для полноты. ошибка, которую вы опубликовали в репозитории Symfony, считается SRP, а не ошибкой. Так что это правильный ответ на ваш вопрос. - person lordrhodos; 17.06.2017
comment
Вы меня уговорили) - person kRicha; 17.06.2017
comment
Я обновил ответ, дайте мне знать, если вы хотите расширить подход к созданию объекта передачи данных или отделить тему пользователя безопасности. - person lordrhodos; 18.06.2017

Ваш токен пользователя, кажется, обновляется формой, даже если ограничение электронной почты останавливает сброс.

Можете ли вы проверить, прошла ли ваша форма функцию isValid? Возможно, вы можете попытаться избежать этого с помощью прослушивателя событий или валидатора.

С помощью события SUBMIT вы сможете проверить целостность электронной почты, а затем добавить FormError, чтобы избежать обновления пользователя.

person Aastal    schedule 13.06.2017
comment
Ты серьезно ? Я понимаю, почему вы все еще застряли на этом, мы здесь, чтобы помочь вам бесплатно, будьте вежливы, пожалуйста. - person Aastal; 14.06.2017
comment
я уже упоминал об обновлении после отправки формы, даже если форма недействительна. Как вы можете видеть в коде: `if ($form-›isSubmitted() && $form-›isValid()) {` есть проверка валидаторов, и for не заполняет данные в объекте, если форма недействительна. - person kRicha; 14.06.2017