Bonjour,

Voila je rencontre un petit problème avec mon code.

Ce que je fais

Donc pour la gestion du profil de l'utilisateur, j'ai fait une page profile qui contient 3 formulaires sous forms de tabs bootstrap.
Dans un premier temps, j'avais fait un controller qui détectait quel form était soumit pour le traiter, mais ça semblait posé problème, alors j'ai fait dans mon controller une fonction qui affiche le profil, et 3 autres qui reçoivent le submit en ajax. ça semble fonctionner car par exemple pour l'email il l'enregistre bien si je le modifie via le formulaire.

Ce que j'obtiens

Mais le souci c'est que le fait de faire en ajax, j'ai perdu au passage le controle des erreur et l'affichage de celles-ci...

Voici le code de tous ça =>

Profilecontroller

<?php

namespace App\Controller;

use App\Entity\UserLanguage;
use App\Service\LanguagesProvider;
use App\Service\CountryProvider;
use App\Form\ProfileAccountType;
use App\Form\ProfileMeType;
use App\Form\ProfileLanguageType;

use Symfony\Component\Routing\Annotation\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; 
use Twig\Environment;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

class ProfileController extends Controller 
{

    /**
     * Get page profile
     * @IsGranted("ROLE_USER")
     */
    public function getProfile(Environment $twig, Request $request, UserPasswordEncoderInterface $passwordEncoder)
    {
        // user
        $user = $this->getUser();
        //Entity Manager
        $em = $this->getDoctrine()->getManager();

        // Build the forms        
        $tab1 = $this->createForm(ProfileAccountType::class, $user, array( 'action' => $this->generateUrl('profileAccount')));
        $tab2 = $this->createForm(ProfileMeType::class,$user, array( 'action' => $this->generateUrl('profileMe')));
        $tab3 = $this->createForm(ProfileLanguageType::class, array( 'action' => $this->generateUrl('profileLanguage')));   

        /*
        Avant ajax commenter au cas où

        // If submit
        if ($request->isXmlHttpRequest()) {  

            if ($request->request->has('profile_account')) {
                $tab1->handleRequest($request);
            } 
            if ($request->request->has('profile_me')) {
                $tab2->handleRequest($request);
            } 
            if ($request->request->has('profile_language')) {
                $tab3->handleRequest($request);
            } 

            // Handle account form
            if ($tab1->isSubmitted() && $tab1->isValid()) {              

                //Get Password submitted
                $passwordSubmit = $tab1->get('password')->getData();
                // Encode
                $password = $passwordEncoder->encodePassword($user, $passwordSubmit);
                // Control and set
                if ($passwordSubmit != null AND $password != $user->getPassword()) {
                    $user->setPassword($password);    
                }

                //Save
                $em->persist($user);
                $em->flush(); 

            // Handle me form
            } else if ($tab2->isSubmitted() && $tab2->isValid()) {

                //Gender
                $gender = $tab2->get('gender')->getData();

                // Control and set
                if ($tab2->get('gender') != null AND $tab2->get('gender') != $user->getGender()) {
                    $user->setGender($gender);    
                }

                //Save
                //$em->persist($user);
                //$em->flush(); 

            // Handle language form
            } else if ($tab3->isSubmitted() && $tab3->isValid()) {

            }

        }

        */

        // View
        return new Response($twig->render('/home/profile.twig', array(
            'user'=>$user, 
            'tab1'=>$tab1->createView(),
            'tab2'=>$tab2->createView(),
            'tab3'=>$tab3->createView(),
            )
        ));

    }

    /**
     * Ajax profileAccount
     * @IsGranted("ROLE_USER")
     * @method({"POST"})
     * @param Request $request
     * @return JsonResponse
     */
    public function profileAccountAction(Request $request, UserPasswordEncoderInterface $passwordEncoder)
    {

        if ($request->isXmlHttpRequest()) {
            // user
            $user = $this->getUser();
            // Entity Manager
            $em = $this->getDoctrine()->getManager();

            $tab1 = $this->createForm(ProfileAccountType::class, $user, array( 'action' => $this->generateUrl('profileAccount')));
            $tab1->handleRequest($request);

            // Form Submitted
            if ($tab1->isValid()) {

                //Get Password submitted
                $passwordSubmit = $tab1->get('password')->getData();

                // Control and set
                if ($passwordSubmit != null ) {
                    // Encode
                    $password = $passwordEncoder->encodePassword($user, $passwordSubmit);
                    if ($password != $user->getPassword()) {
                        $user->setPassword($password);    
                    }
                } 

                //Save
                $em->persist($user);
                $em->flush(); 

                return new JsonResponse(array('message' => $errorCollection));

            } else {
                //dump($tab1->getErrors());
                /*$errorCollection = array();
                foreach($errors as $error){
                       $errorCollection[] = $error->getMessageTemplate();
                };*/
                return new JsonResponse(array('message' => $tab1->getErrors()));
            }

        } else {
            return new JsonResponse(array('message' => 'not XmlHttpRequest'));
        }

    }

    /**
     * Ajax profileMe
     * @IsGranted("ROLE_USER")
     */
    public function profileMeAction(Request $request)
    {

    }

    /**
     * Ajax profileLanguage
     * @IsGranted("ROLE_USER")
     */
    public function profileLanguageAction(Request $request)
    {

    }
}

Exemple de formType pour la tab Account

<?php

namespace App\Form;

use App\Entity\User;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\RadioType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\LanguageType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\CountryType;

use Symfony\Component\Intl\Intl;

class ProfileAccountType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder

            /* Tab My account */
            ->add('username', EmailType::class, array(
                'label' => 'profile.account.email', 'required'=> true
                ))
            ->add('nickname', TextType::class, array(
                'label' => 'profile.account.nickname', 'required'=> true
            ))  
            ->add('password', RepeatedType::class, array(
                'type' => PasswordType::class,
                'invalid_message' => 'profile.account.invalidPassword',
                'mapped' =>false,
                'required' => false,
                'first_options'  => array('label' => 'profile.account.password'),
                'second_options' => array('label' => 'profile.account.repeatPassword')
                ))
        ;         

    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => User::class
        ));
    }

    public function getName() {
        return 'profile_account';
    }
}

Twig

{% extends 'layout/base.twig' %}

{% block stylesheets %}
    <link href="{{ asset('css/home/profile.css') }}" rel="stylesheet">
{% endblock %}

{% block content %}  
  <div class="container">

    <ul class="nav nav-tabs" id="myTab" role="tablist">
        <li class="nav-item ">
            <a class="nav-link active" id="account-tab" data-toggle="tab" href="#account" role="tab" aria-controls="account" aria-selected="true">{{ 'profile.account.tab'|trans }}</a>
        </li>
        <li class="nav-item">
            <a class="nav-link" id="profile-tab" data-toggle="tab" href="#me" role="tab" aria-controls="profile" aria-selected="false">{{ 'profile.me.tab'|trans }}</a>
        </li>
        <li class="nav-item">
            <a class="nav-link" id="language-tab" data-toggle="tab" href="#language" role="tab" aria-controls="language" aria-selected="false">{{ 'profile.language.tab'|trans }}</a>
        </li>
    </ul>

    <div class="tab-content" id="myTabContent">

        <!-- account -->
        <div class="tab-pane fade show active" id="account" role="tabpanel" aria-labelledby="account-tab">

            {{ form_start(tab1, {'attr': {'id': 'profile_account'}}) }}
                <div class="form-group ">
                    {{ form_row(tab1.username, {attr: {class: 'form-control'} }) }}
                </div>
                <div class="form-group ">
                    {{ form_row(tab1.nickname, {attr: {class: 'form-control'} }) }}
                </div>
                <div class="form-group ">
                    {{ form_row(tab1.password, {attr: {class: 'form-control'} }) }}
                </div>      

                <div id="errorAccount" class="form-group alert alert-danger"></div>

                <button type="submit" id="btnAccount" class="btn btn-md btn-secondary yellowButton">{{ 'profile.btnSave'|trans }}</button>

                <!-- add token CSRF (hidden) -->
                {{ form_rest(tab1) }}
            {{ form_end(tab1) }}

        </div>

        <!-- me -->
        <div class="tab-pane fade" id="me" role="tabpanel" aria-labelledby="profile-tab">
            {{ form_start(tab2, {'attr': {'id': 'profile_me'}}) }}
                <div class="form-group ">
                    {{ form_row(tab2.avatar, {attr: {class: 'form-control'} }) }}
                </div>
                <div class="form-group ">
                    {{ form_row(tab2.presentation, {attr: {class: 'form-control'} }) }}   
                </div>
                <div class="form-group ">
                    {{ form_row(tab2.country, {attr: {class: 'form-control'} }) }}
                </div>      
                <div class="form-group ">
                    {{ form_row(tab2.city, {attr: {class: 'form-control'} }) }}
                </div>
                <div class="form-group ">
                    {{ form_row(tab2.gender, {attr: {class: 'form-control'} }) }}
                </div>

                <button type="submit" id="btnMe" class="btn btn-md btn-secondary yellowButton">{{ 'profile.btnSave'|trans }}</button>

                <!-- add token CSRF (hidden) -->
                {{ form_rest(tab2) }}
            {{ form_end(tab2) }}
        </div>

        <!-- language -->
        <div class="tab-pane fade" id="language" role="tabpanel" aria-labelledby="contact-tab">
            {{ form_start(tab3, {'attr': {'id': 'profile_language'}}) }}
                <div class="form-group">
                    {{ form_row(tab3.allLanguages, {attr: {class: 'form-control'} }) }}
                </div>
                <div class="form-group">
                    {{ form_row(tab3.level, {attr: {class: 'form-control'} }) }}
                </div>

                <button type="submit" id="btnLanguage" class="btn btn-md btn-secondary yellowButton">{{ 'profile.btnSave'|trans }}</button>      

                <!-- add token CSRF (hidden) -->
                {{ form_rest(tab3) }}
            {{ form_end(tab3) }}
        </div>

    </div>

  </div>
{% endblock %}

{% block javascripts %}
    <script type="text/javascript" src="{{ asset('js/profile.js') }}" ></script>
{% endblock %}

profile.js

/* Profile tabs */
$(document).ready(function () {

    /* Form Account tab */
    $("#profile_account").submit(function (e) {

        var isvalidate = $("#profile_account")[0].checkValidity();

        if (isvalidate) {

            $.post('profileAccount',$("#profile_account").serialize(),function(data) {
                    console.log(data.message);
                    $('#errorAccount').text(data.message);
            }, 'JSON');

        } 
        e.preventDefault();
    });

});

Voilà, merci pour votre aide

3 réponses


laplumaencre
Auteur
Réponse acceptée

Voilà tous fonctionne (enfin pour le tab Account) !

voici le code du controleur qui reçoit la requete ajax =>

/**
     * Ajax profileAccount
     * @IsGranted("ROLE_USER")
     * @method({"POST"})
     * @param Request $request
     * @return JsonResponse
     */
    public function profileAccountAction(Request $request, UserPasswordEncoderInterface $passwordEncoder)
    {

        if ($request->isXmlHttpRequest()) {

            // user
            $user = $this->getUser();
            // Entity Manager
            $em = $this->getDoctrine()->getManager();

            $tab1 = $this->createForm(ProfileAccountType::class, $user, array( 'action' => $this->generateUrl('profileAccount')));
            $tab1->handleRequest($request);

            // Form Submitted
            if ($tab1->isValid()) {

                $modified = false;

                //Get Password submitted
                $passwordSubmit = $tab1->get('password')->getData();
                $emailSubmit = $tab1->get('username')->getData();
                $nicknameSubmit = $tab1->get('nickname')->getData();

                // Control and set
                if ($passwordSubmit != null ) {
                    // Encode
                    $password = $passwordEncoder->encodePassword($user, $passwordSubmit);

                    if ($password != $user->getPassword()) {
                        $user->setPassword($password);    
                        $modified = true;
                    }
                }

                if ($emailSubmit != $user->getUsername() ) {

                    $user->setUsername($emailSubmit);    
                    $modified = true;
                }

                if ($nicknameSubmit != $user->getNickname() ) {

                    $user->setNickname($nicknameSubmit);    
                    $modified = true;
                }

                if ($modified) {

                    //Save
                    $em->persist($user);
                    $em->flush(); 

                    // Add message in form
                    $tab1->addError(new FormError($this->translator->trans('profile.account.ok')));

                } else {
                    // Add message in form
                    $tab1->addError(new FormError($this->translator->trans('profile.account.unmodified')));
                }

                // Response
                // Get errors translate
                $error = $this->ErrorProvider->getErrorMessages($tab1);
                return new JsonResponse(array('message' => $error));

            } else {

                // error isValid
                $error = $this->ErrorProvider->getErrorMessages($tab1);     
                return new JsonResponse($error);
            }

        } else {
            return new JsonResponse(array('message' => 'not XmlHttpRequest'));
        }

    }

Error provider

<?php

namespace App\Service;

class ErrorProvider 
{

    public function getErrorMessages(\Symfony\Component\Form\Form $form) {

        $errors = array();

        foreach ($form->getErrors() as $key => $error) {
            $template = $error->getMessage();
            $parameters = $error->getMessageParameters();

            foreach ($parameters as $var => $value) {
                $template = str_replace($var, $value, $template);
            }

            $errors[$key] = $template;
        }

        if ($form->count()) {
            foreach ($form as $child) {
                if (!$child->isValid()) {
                    $errors[$child->getName()] = $this->getErrorMessages($child);
                }
            }
        }

        return $errors;
    }

}

Le js =>

/* Profile tabs */
// Hide div error message
$('#errorAccount').hide();

$(document).ready(function () {

    /* Form Account tab */
    $("#profile_account").submit(function (e) {

        var isvalidate = $("#profile_account")[0].checkValidity();

        if (isvalidate) {

            $.post('profileAccount',$("#profile_account").serialize(),function(data) {

                $('#errorAccount').text('');

                if (data.message) {

                    $('#errorAccount').text(data.message);

                } else {

                    $.each(data,function() {
                        $.each(this, function (name, value) {
                            $('#errorAccount').text(value);
                        });
                    })

                }
                $('#errorAccount').show('slow');

            }, 'JSON');

        } 
        e.preventDefault();
    });

});

Voilà j'espère que ça en aidera d'autre, merci pour ceux qui sont intervenu !

tu dois faire la verification des 2 cotés donc coté server et coté client, coté server tu peux utiliser les Assertions de SF et coté client avec JS, en fonction de sa tu pourras soit retourné un json pour qu'il soit traité avec ton ajax et l'afficher sur la page ou soit coté server, faire une redirection ou retourné une vue avec un message flash avec le message de ton Assertion

Salut gallarian, merci pour ta réponse.
J'ai pas tout saisi là où tu voulais en venir. en fait le controller reçoit bien l'appel ajax et semble bien renvoyer les messages (erreur ou ok), mais je m'y suis peut etre mal pris.
Déjà est ce que le fait que mon controller ait 4 fonctions (une pour afficher profile par défaut, et 3 autres, une par formulaire pour le traitement en ajax) est-il bon, ou je devrais faire tout sur le meme fonction (meme route)?
Et si je laisse comme ça, comment continuer d'afficher les erreurs que symfony renvoie du genre "les mots de passe doivent être identiques" comme quand je ne passe pas le traitement du form en ajax ?

EDIT :
image du résultat dans profil => http://nimb.ws/3UcGZv

Maintenant j'ai une erreur qui apparait, mais elle n'est pas traduite...
Fonction pour récupérer les erreurs du form tab1 =>

private function getErrorMessages(\Symfony\Component\Form\Form $form) {
        $errors = array();
        $translator = $this->translator;
        foreach ($form->getErrors() as $key => $error) {
            $template = $error->getMessageTemplate();
            $parameters = $error->getMessageParameters();

            foreach ($parameters as $var => $value) {
                $template = str_replace($var, $value, $template);
            }

            $errors[$key] = $template;
        }
        if ($form->count()) {
            foreach ($form as $child) {
                if (!$child->isValid()) {
                    $errors[$child->getName()] = $this->getErrorMessages($child);
                }
            }
        }

        return $errors;
    }

Reponse en JSONResponse =>

$errors = $this->getErrorMessages($tab1);

                return new JsonResponse(array('message' => $errors));

Js qui récupère =>

/* Profile tabs */
// Hide div error message
$('#errorAccount').hide();

$(document).ready(function () {

    /* Form Account tab */
    $("#profile_account").submit(function (e) {

        var isvalidate = $("#profile_account")[0].checkValidity();

        if (isvalidate) {

            $.post('profileAccount',$("#profile_account").serialize(),function(data) {

                $('#errorAccount').text('');

                if (data.message) {

                    $('#errorAccount').text(data.message);

                } else {

                    $.each(data,function() {
                        $.each(this, function (name, value) {
                            $('#errorAccount').text(value);
                        });
                    })

                }
                $('#errorAccount').show('slow');

            }, 'JSON');

        } 
        e.preventDefault();
    });

});

Et on voit le résultat sur la photo plus haut...