Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Editing User with error wrongly changes the app.user.username temporarily, how to solve it?

We use the Symfony2 framework and FOSUserBundle for our users. So We have our own UserBundle that inherits from FOSUserBundle. The problem is : When we send the form for editing a user with a wrong password, the app.user.username that is displayed in the header changes when it shouldn't since the form isn't correct. Then when we reload the page, the app.user.username has the normal username (since it hasn't changed in the database). The question is, why does the app.user.username changes temporarily for the value of the form (edit user) that we just sent with a wrong password ?

we use the normal routing from FOSUser :

fos_user_security:
    resource: "@FOSUserBundle/Resources/config/routing/security.xml"

fos_user_profile:
    resource: "@FOSUserBundle/Resources/config/routing/profile.xml"
    prefix: /profile

fos_user_register:
    resource: "@FOSUserBundle/Resources/config/routing/registration.xml"
    prefix: /register

fos_user_resetting:
    resource: "@FOSUserBundle/Resources/config/routing/resetting.xml"
    prefix: /resetting

fos_user_change_password:
    resource: "@FOSUserBundle/Resources/config/routing/change_password.xml"
    prefix: /profile

And the default Controller from FOSUser :

public function editAction(Request $request)
{
    $user = $this->getUser();
    if (!is_object($user) || !$user instanceof UserInterface) {
        throw new AccessDeniedException('This user does not have access to this section.');
    }

    /** @var $dispatcher \Symfony\Component\EventDispatcher\EventDispatcherInterface */
    $dispatcher = $this->get('event_dispatcher');

    $event = new GetResponseUserEvent($user, $request);
    $dispatcher->dispatch(FOSUserEvents::PROFILE_EDIT_INITIALIZE, $event);

    if (null !== $event->getResponse()) {
        return $event->getResponse();
    }

    /** @var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
    $formFactory = $this->get('fos_user.profile.form.factory');

    $form = $formFactory->createForm();
    $form->setData($user);

    $form->handleRequest($request);

    if ($form->isValid()) {
        /** @var $userManager \FOS\UserBundle\Model\UserManagerInterface */
        $userManager = $this->get('fos_user.user_manager');

        $event = new FormEvent($form, $request);
        $dispatcher->dispatch(FOSUserEvents::PROFILE_EDIT_SUCCESS, $event);

        $userManager->updateUser($user);

        if (null === $response = $event->getResponse()) {
            $url = $this->generateUrl('fos_user_profile_show');
            $response = new RedirectResponse($url);
        }

        $dispatcher->dispatch(FOSUserEvents::PROFILE_EDIT_COMPLETED, new FilterUserResponseEvent($user, $request, $response));

        return $response;
    }

    return $this->render('FOSUserBundle:Profile:edit.html.twig', array(
        'form' => $form->createView()
    ));
}

Here is the part of code that is displayed in the header that should NOT change when submitting the form with a wrong password. (app.user.username)

<ul class="nav navbar-nav navbar-right">
    {% if is_granted("IS_AUTHENTICATED_REMEMBERED") %}
    <li>
        <a href="{{ path('fos_user_profile_show') }}">{{ app.user.username }}
        </a>
    </li>
    <li>
        <a href="{{ path('fos_user_security_logout') }}">
                {{ 'layout.logout'|trans({}, 'FOSUserBundle') }}
        </a>
     </li>
     {% else %}
     <li>
        <a href="{{ path('fos_user_registration_register') }}">{{ 'layout.register'|trans({}, 'FOSUserBundle') }}</a>
     </li>
     <li>
        <a href="{{ path('fos_user_security_login') }}">{{ 'layout.login'|trans({}, 'FOSUserBundle') }}</a>
      </li>
      {% endif %}
</ul>

And Here is the code of the formulaire that is displayed :

<div class="well">

{{ form_start(form, {'attr': {'class': 'form-horizontal'}}) }}

{{ form_errors(form) }}

<div class="form-group">
  {# Génération du label username. #}
  {{ form_label(form.username, 'register.form.username'|trans, {'label_attr': {'class': 'col-sm-3 control-label'}}) }}

  {# Affichage des erreurs pour ce champ précis. #}
  {{ form_errors(form.username) }}

  <div class="col-sm-4">
    {{ form_widget(form.username, {'attr': {'class': 'form-control'}}) }}
  </div>
</div>

  <div class="form-group">
  {# Génération du label adresse. #}
  {{ form_label(form.address, 'register.form.address'|trans, {'label_attr': {'class': 'col-sm-3 control-label'}}) }}

  {# Affichage des erreurs pour ce champ précis. #}
  {{ form_errors(form.address) }}

  <div class="col-sm-4">
    {{ form_widget(form.address, {'attr': {'class': 'form-control'}}) }}
  </div>
</div>

  <div class="form-group">
  {# Génération du label nom du contact. #}
  {{ form_label(form.contactName, 'register.form.contact_name'|trans, {'label_attr': {'class': 'col-sm-3 control-label'}}) }}

  {# Affichage des erreurs pour ce champ précis. #}
  {{ form_errors(form.contactName) }}

  <div class="col-sm-4">
    {{ form_widget(form.contactName, {'attr': {'class': 'form-control'}}) }}
  </div>
</div>

  <div class="form-group">
  {# Génération du label email. #}
  {{ form_label(form.email, 'register.form.email'|trans, {'label_attr': {'class': 'col-sm-3 control-label'}}) }}

  {# Affichage des erreurs pour ce champ précis. #}
  {{ form_errors(form.email) }}

  <div class="col-sm-4">
    {{ form_widget(form.email, {'attr': {'class': 'form-control'}}) }}
  </div>
  </div>

  <div class="form-group">
  {# Génération du label numéro de téléphone. #}
  {{ form_label(form.phone, 'register.form.phone'|trans, {'label_attr': {'class': 'col-sm-3 control-label'}}) }}

  {# Affichage des erreurs pour ce champ précis. #}
  {{ form_errors(form.phone) }}

  <div class="col-sm-4">
    {{ form_widget(form.phone, {'attr': {'class': 'form-control'}}) }}
  </div>
  </div>

  <div class="form-group">
  {# Génération du label mot de passe. #}
  {{ form_label(form.current_password, 'register.form.password'|trans, {'label_attr': {'class': 'col-sm-3 control-label'}}) }}

  {# Affichage des erreurs pour ce champ précis. #}
  {{ form_errors(form.current_password) }}

  <div class="col-sm-4">
    {{ form_widget(form.current_password, {'attr': {'class': 'form-control'}}) }}
  </div>
</div>

{# Pour le bouton, pas de label ni d'erreur, on affiche juste le widget #}
{{ form_widget(form.saveButton, {'attr': {'class': 'btn btn-primary'},  'label': 'profile.edit.submit'|trans }) }}

{# Génération automatique des champs pas encore écrits.
 Dans cet exemple, ce serait le champ CSRF (géré automatiquement par Symfony !)
 et tous les champs cachés (type « hidden »). #}
{{ form_rest(form) }}

{# Fermeture de la balise <form> du formulaire HTML #}
{{ form_end(form) }}

</div>

And here is the FormType that we user for this particular form :

public function buildForm(FormBuilderInterface $builder, array $options)
{
    // add your custom field
    $builder->add('contactName', 'text')
            ->add('phone', 'text')
            ->add('address', 'text')
            ->add('saveButton', 'submit');
}

The FormType inherits from ProfileFormType (Default in FOSUserBundle) thanks to a getParent() function.

Thank you for your help figuring out what is wrong. We don't understand why the app.user.username changes in the header since the form is sent with a wrong password and the data in the database isn't changed and sorry for the comments that are in French in the code :).

like image 939
Bad Wolf Avatar asked Nov 01 '22 14:11

Bad Wolf


1 Answers

This works for me:

1) Add currentUsername property and lifecycle callbacks into User entity class

namespace AppBundle\Entity;

use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;

/**
* Class User
* @package AppBundle\Entity
*
* @ORM\Entity(repositoryClass="AppBundle\Repositories\User")
* @ORM\Table(name="site_user")
* @ORM\HasLifecycleCallbacks()
*/
class User extends BaseUser
{
    /**
     * @var int
     *
     * @ORM\Column(type="integer", nullable=false)
     * @ORM\Id()
     * @ORM\GeneratedValue()
     */
    protected $id;

    /**
     * @var string
     */
    public $currentUsername;

    /**
     * @ORM\PostLoad()
     * @ORM\PostUpdate()
     * @ORM\PostPersist()
     */
    public function syncCurrentUsername()
    {
        $this->currentUsername = $this->username;
    }

    /**
     * @return string
     */
    public function getCurrentUsername()
    {
        return $this->currentUsername;
    }

}

2) And then in template use:

{{ app.user.getCurrentUsername() }}

3) In some cases maybe you should need to override serialize and deserialize methods and add new property (currentUsername) to it.

like image 78
denis.isaev Avatar answered Nov 15 '22 05:11

denis.isaev