Я новичок в Symfony 3.4, и мне было поручено добавить функцию сброса пароля на существующий веб-сайт.Я следовал руководству по использованию friendsofsymfony / user-bundle и дошел до того, что отправляю запрос Json своему контроллеру, и он отвечает обратно:
{"code":500,"message":"Unrecognized field: emailCanonical"}
Строка, вызывающая проблему:
$user = $this->get('fos_user.user_manager')->findUserByUsernameOrEmail($username);
Это полный файл src / AppBundle / RestPasswordManagementController.php
<?php
namespace AppBundle\Controller;
use FOS\RestBundle\Controller\Annotations;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Routing\ClassResourceInterface;
use FOS\RestBundle\Controller\Annotations\RouteResource;
use FOS\UserBundle\Event\FilterUserResponseEvent;
use FOS\UserBundle\Event\GetResponseNullableUserEvent;
use FOS\UserBundle\Event\GetResponseUserEvent;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
/**
* @Annotations\Prefix("password")
* @RouteResource("/password", pluralize=false)
*/
class RestPasswordManagementController extends FOSRestController implements ClassResourceInterface
{
/**
* @Annotations\Post("/reset/request")
*/
public function requestResetAction(Request $request)
{
$username = $request->get('email');
/** @var $user UserInterface */
$user = $this->get('fos_user.user_manager')->findUserByUsernameOrEmail($username);
return new JsonResponse(
$user,
JsonResponse::HTTP_FORBIDDEN
);
/** @var $dispatcher EventDispatcherInterface */
$dispatcher = $this->get('event_dispatcher');
/* Dispatch init event */
$event = new GetResponseNullableUserEvent($user, $request);
$dispatcher->dispatch(FOSUserEvents::RESETTING_SEND_EMAIL_INITIALIZE, $event);
if (null !== $event->getResponse()) {
return $event->getResponse();
}
if (null === $user) {
return new JsonResponse(
'User not recognised',
JsonResponse::HTTP_FORBIDDEN
);
}
$event = new GetResponseUserEvent($user, $request);
$dispatcher->dispatch(FOSUserEvents::RESETTING_RESET_REQUEST, $event);
if (null !== $event->getResponse()) {
return $event->getResponse();
}
if ($user->isPasswordRequestNonExpired($this->container->getParameter('fos_user.resetting.token_ttl'))) {
return new JsonResponse(
$this->get('translator')->trans('resetting.password_already_requested', [], 'FOSUserBundle'),
JsonResponse::HTTP_FORBIDDEN
);
}
if (null === $user->getConfirmationToken()) {
/** @var $tokenGenerator \FOS\UserBundle\Util\TokenGeneratorInterface */
$tokenGenerator = $this->get('fos_user.util.token_generator');
$user->setConfirmationToken($tokenGenerator->generateToken());
}
/* Dispatch confirm event */
$event = new GetResponseUserEvent($user, $request);
$dispatcher->dispatch(FOSUserEvents::RESETTING_SEND_EMAIL_CONFIRM, $event);
if (null !== $event->getResponse()) {
return $event->getResponse();
}
$this->get('fos_user.mailer')->sendResettingEmailMessage($user);
$user->setPasswordRequestedAt(new \DateTime());
$this->get('fos_user.user_manager')->updateUser($user);
/* Dispatch completed event */
$event = new GetResponseUserEvent($user, $request);
$dispatcher->dispatch(FOSUserEvents::RESETTING_SEND_EMAIL_COMPLETED, $event);
if (null !== $event->getResponse()) {
return $event->getResponse();
}
return new JsonResponse(
$this->get('translator')->trans(
'resetting.check_email',
[ '%tokenLifetime%' => floor($this->container->getParameter('fos_user.resetting.token_ttl') / 3600) ],
'FOSUserBundle'
),
JsonResponse::HTTP_OK
);
}
/**
* Reset user password
* @Annotations\Post("/reset/confirm")
*/
public function confirmResetAction(Request $request)
{
$token = $request->request->get('token', null);
if (null === $token) {
return new JsonResponse('You must submit a token.', JsonResponse::HTTP_BAD_REQUEST);
}
/** @var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
$formFactory = $this->get('fos_user.resetting.form.factory');
/** @var $userManager \FOS\UserBundle\Model\UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');
/** @var $dispatcher \Symfony\Component\EventDispatcher\EventDispatcherInterface */
$dispatcher = $this->get('event_dispatcher');
$user = $userManager->findUserByConfirmationToken($token);
if (null === $user) {
return new JsonResponse(
// no translation provided for this in \FOS\UserBundle\Controller\ResettingController
sprintf('The user with "confirmation token" does not exist for value "%s"', $token),
JsonResponse::HTTP_BAD_REQUEST
);
}
$event = new GetResponseUserEvent($user, $request);
$dispatcher->dispatch(FOSUserEvents::RESETTING_RESET_INITIALIZE, $event);
if (null !== $event->getResponse()) {
return $event->getResponse();
}
$form = $formFactory->createForm([
'csrf_protection' => false,
'allow_extra_fields' => true,
]);
$form->setData($user);
$form->submit($request->request->all());
if (!$form->isValid()) {
return $form;
}
$event = new FormEvent($form, $request);
$dispatcher->dispatch(FOSUserEvents::RESETTING_RESET_SUCCESS, $event);
$userManager->updateUser($user);
if (null === $response = $event->getResponse()) {
return new JsonResponse(
$this->get('translator')->trans('resetting.flash.success', [], 'FOSUserBundle'),
JsonResponse::HTTP_OK
);
}
// unsure if this is now needed / will work the same
$dispatcher->dispatch(FOSUserEvents::RESETTING_RESET_COMPLETED, new FilterUserResponseEvent($user, $request, $response));
return new JsonResponse(
$this->get('translator')->trans('resetting.flash.success', [], 'FOSUserBundle'),
JsonResponse::HTTP_OK
);
}
/**
* Change user password
*
* @ParamConverter("user", class="AppBundle:User")
*
* @Annotations\Post("/{user}/change")
*/
public function changeAction(Request $request, UserInterface $user)
{
if ($user !== $this->getUser()) {
throw new AccessDeniedHttpException();
}
/** @var $dispatcher \Symfony\Component\EventDispatcher\EventDispatcherInterface */
$dispatcher = $this->get('event_dispatcher');
$event = new GetResponseUserEvent($user, $request);
$dispatcher->dispatch(FOSUserEvents::CHANGE_PASSWORD_INITIALIZE, $event);
if (null !== $event->getResponse()) {
return $event->getResponse();
}
/** @var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
$formFactory = $this->get('fos_user.change_password.form.factory');
$form = $formFactory->createForm([
'csrf_protection' => false
]);
$form->setData($user);
$form->submit($request->request->all());
if ( ! $form->isValid()) {
return $form;
}
/** @var $userManager \FOS\UserBundle\Model\UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');
$event = new FormEvent($form, $request);
$dispatcher->dispatch(FOSUserEvents::CHANGE_PASSWORD_SUCCESS, $event);
$userManager->updateUser($user);
if (null === $response = $event->getResponse()) {
return new JsonResponse(
$this->get('translator')->trans('change_password.flash.success', [], 'FOSUserBundle'),
JsonResponse::HTTP_OK
);
}
$dispatcher->dispatch(FOSUserEvents::CHANGE_PASSWORD_COMPLETED, new FilterUserResponseEvent($user, $request, $response));
return new JsonResponse(
$this->get('translator')->trans('change_password.flash.success', [], 'FOSUserBundle'),
JsonResponse::HTTP_OK
);
}
}
Форма longin не использует тот же Bundle.На всякий случай это может помочь, вот файл: src / AppBundle / Security / LoginFormAuthenticator.php
<?php
namespace AppBundle\Security;
use AppBundle\Form\LoginForm;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
private $formFactory;
private $em;
private $router;
private $passwordEncoder;
public function __construct(FormFactoryInterface $formFactory, EntityManager $em, RouterInterface $router, UserPasswordEncoder $passwordEncoder)
{
$this->formFactory = $formFactory;
$this->em = $em;
$this->router = $router;
$this->passwordEncoder = $passwordEncoder;
}
public function getCredentials(Request $request)
{
$isLoginSubmit = $request->getPathInfo() == '/login' && $request->isMethod('POST');
if (!$isLoginSubmit) {
// skip authentication
return;
}
$form = $this->formFactory->create(LoginForm::class);
$form->handleRequest($request);
$data = $form->getData();
$request->getSession()->set(
Security::LAST_USERNAME,
$data['_username']
);
return $data;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$username = $credentials['_username'];
return $this->em->getRepository('AppBundle:User')
->findOneBy(['email' => $username]);
}
public function checkCredentials($credentials, UserInterface $user)
{
$password = $credentials['_password'];
if ($this->passwordEncoder->isPasswordValid($user, $password)) {
return true;
}
return false;
}
protected function getLoginUrl()
{
return $this->router->generate('security_login');
}
protected function getDefaultSuccessRedirectUrl()
{
return $this->router->generate('app-dashboard');
}
}
Что касается представления, то оно довольно простое.Существует сценарий jQuery, который переключается между формой входа и утерянным паролем.
{% extends '::base.html.twig' %}
{# -- PAGE TITLE -- #}
{% block title %}
{{ title }}
{% endblock %}
{# -- PAGE CONTENT -- #}
{% block body %}
<div class="account-pages"></div>
<div class="clearfix"></div>
<div class="wrapper-page">
<div class="account-bg">
<div class="card-box m-b-0">
<div class="text-xs-center">
<a href="/" class="logo">
<img src="{{ asset('assets/images/app/login-logo.png') }}" alt="SSTenligne">
</a>
</div>
<div class="m-t-10 p-20 login_form">
{{ form_start(form, {'autocomplete': 'off'}) }}
{{ form_row(form._username) }}
{{ form_row(form._password) }}
<div class="form-group text-center row m-t-30">
<div class="col-xs-12">
<button class="btn btn-primary btn-block waves-effect waves-light" type="submit">{{ 'Login'|trans }}</button>
</div>
</div>
{{ form_end(form) }}
<a class="mdp_resset" href="#">Mot de passe perdu?</a>
</div>
<div class="m-t-10 p-20 password_resset">
{{ form_start(form2, {'autocomplete': 'off'}) }}
{{ form_row(form2._username_resset) }}
<div class="form-group text-center row m-t-30">
<div class="col-xs-12">
<button class="btnResset btn btn-primary btn-block waves-effect waves-light" type="submit">{{ 'Récupération'|trans }}</button>
</div>
</div>
{{ form_end(form2) }}
<a class="mdp_login" href="#">Retour</a>
</div>
<div class="clearfix"></div>
</div>
</div>
<!-- end card-box-->
</div>
<!-- end wrapper page -->
{% endblock %}
{# -- SPECIFIC SCRIPTS -- #}
{% block scripts %}
<script>
{% if error %}
toastr.error('{{ error.messageKey|trans(error.messageData, 'security') }}');
{% endif %}
$(".password_resset form").submit(function(e){
e.preventDefault();
$.ajax({
type: "POST",
dataType: "JSON",
url: "password/reset/request",
data:{
email:$("#resset_form__username_resset").val()
},
error: function (jql, code, returnMsgError) {
alert('Une erreur est survenue');
},
success: function ($data) {
alert('It\'s posted!');
}
});
});
</script>
{% endblock %}
Запрос отправлен на пароль / сброс / запрос, и там я получаю сообщение об ошибке:
{"code ": 500," message ":" Нераспознанное поле: emailCanonical "}
Я новичок в Symfony и не знаю, с чего начать отладку этой проблемы.Любая помощь будет высоко ценится.Если вам нужно, чтобы я показал больше кода, чтобы помочь мне найти решение, оставьте меня в курсе, и я отредактирую.
РЕДАКТИРОВАТЬ: Поэтому, как указано в комментариях, я начал реализовывать пользовательский Canonicalizer
В моем services.yml я добавил
app.my_canonicalizer:
class: AppBundle\Util\MyCanonicalizer
arguments: ['@normalize_service']
public: false
В своем конфиге я добавил
# FOS User
fos_user:
db_driver: orm # other valid values are 'mongodb', 'couchdb' and 'propel'
firewall_name: api
user_class: AppBundle\Entity\User
from_email:
address: "[my_email]"
sender_name: "[my_name]"
service:
mailer: user.mailer.rest
email_canonicalizer: app.my_canonicalizer
username_canonicalizer: app.my_canonicalizer
, а затем создал файлы с именем: AppBundle / Util / MyCanonicalizer.php
<?php
namespace AppBundle\Util;
use FOS\UserBundle\Util\CanonicalizerInterface;
class MyCanonicalizer implements CanonicalizerInterface
{
private $normalizeService;
public function __construct(NormalizeService $normalizeService)
{
$this->normalizeService = $normalizeService;
}
public function canonicalize($string)
{
if (null === $string) {
return null;
}
return $this->normalizeService->normalize($string);
}
}
?>
И когда я отправляю запрос, я получаю эту ошибку:
<b>Fatal error</b>: Uncaught Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: The service "fos_user.util.email_canonicalizer" has a dependency on a non-existent service "normalize_service". in /home/website/public_html/intranet/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php:31
Stack trace:
#0 /home/website/public_html/intranet/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php(60): Symfony\Component\DependencyInjection\Compiler\CheckExceptionOnInvalidReferenceBehaviorPass->processValue(Object(Symfony\Component\DependencyInjection\Reference), false)
#1 /home/website/public_html/intranet/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php(28): Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass->processValue(Array, false)
#2 /home/website/public_html/intranet/vendor/sym in <b>/home/website/public_html/intranet/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php</b> on line <b>31</b><br />