Bad Wolf Bad Wolf - 3 months ago 28
PHP Question

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 :).

Answer

This is happening by design. At the moment you call $form->handleRequest($request) the user entity is updated with the submitted form data. When you access app.user you are in fact accessing the same user entity. The docs have some more detail about this