Pages :
Bonjour,
Mon binôme et moi sommes sur la dernière ligne droite de notre formation et nous travaillons sur un mini projet d'application de jeu de Quizz avec pour chaque Quizz, 10 Questions, pour chaque Question, on peut avoir 4 propositions de réponses.
Notre MCD est bon, nos entités aussi ainsi que leur relations.
Nous avançons en nous répartissant les tâches.
Là j'en suis donc rendu au moment où un utilisateur identifié (donc en Bdd) peut proposer son nouveau Quizz avec toutes les Questions et propositions de réponses.
Voici le Quizz Entity :
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * @ORM\Entity(repositoryClass="App\Repository\QuizzRepository")
 */
class Quizz
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=128)
     */
    private $title;

    /**
     * @ORM\Column(type="string", length=128)
     */
    private $slug;

    /**
     * @ORM\Column(type="text", nullable=true)
     */
    private $description;

    /**
     * @ORM\Column(type="boolean", nullable=true)
     */
    private $IsPrivate;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="quizzs")
     */
    private $category;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Question", mappedBy="quizz", orphanRemoval=true)
     */
    private $questions;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\IsLike", mappedBy="quizz")
     */
    private $isLikes;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Statistic", mappedBy="quizz", orphanRemoval=true)
     */
    private $statistics;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="quizzs")
     * @ORM\JoinColumn(nullable=false)
     */
    private $author;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Crew", inversedBy="quizzs")
     */
    private $crew;

    public function __construct()
    {
        $this->questions = new ArrayCollection();
        $this->isLikes = new ArrayCollection();
        $this->statistics = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTitle(): ?string
    {
        return $this->title;
    }

    public function setTitle(string $title): self
    {
        $this->title = $title;

        return $this;
    }

    public function getSlug(): ?string
    {
        return $this->slug;
    }

    public function setSlug(string $slug): self
    {
        $this->slug = $slug;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(?string $description): self
    {
        $this->description = $description;

        return $this;
    }

    public function getIsPrivate() : ? bool
    {
        return $this->IsPrivate;
    }

    public function setIsPrivate(? bool $IsPrivate) : self
    {
        $this->IsPrivate = $IsPrivate;

        return $this;
    }

    public function getCategory(): ?Category
    {
        return $this->category;
    }

    public function setCategory(?Category $category): self
    {
        $this->category = $category;

        return $this;
    }

    /**
     * @return Collection|Question[]
     */
    public function getQuestions(): Collection
    {
        return $this->questions;
    }

    public function addQuestion(Question $question): self
    {
        if (!$this->questions->contains($question)) {
            $this->questions[] = $question;
            $question->setQuizz($this);
        }

        return $this;
    }

    public function removeQuestion(Question $question): self
    {
        if ($this->questions->contains($question)) {
            $this->questions->removeElement($question);
            // set the owning side to null (unless already changed)
            if ($question->getQuizz() === $this) {
                $question->setQuizz(null);
            }
        }

        return $this;
    }

    /**
     * @return Collection|IsLike[]
     */
    public function getIsLikes(): Collection
    {
        return $this->isLikes;
    }

    public function addIsLike(IsLike $isLike): self
    {
        if (!$this->isLikes->contains($isLike)) {
            $this->isLikes[] = $isLike;
            $isLike->setQuizz($this);
        }

        return $this;
    }

    public function removeIsLike(IsLike $isLike): self
    {
        if ($this->isLikes->contains($isLike)) {
            $this->isLikes->removeElement($isLike);
            // set the owning side to null (unless already changed)
            if ($isLike->getQuizz() === $this) {
                $isLike->setQuizz(null);
            }
        }

        return $this;
    }

    /**
     * @return Collection|Statistic[]
     */
    public function getStatistics(): Collection
    {
        return $this->statistics;
    }

    public function addStatistic(Statistic $statistic): self
    {
        if (!$this->statistics->contains($statistic)) {
            $this->statistics[] = $statistic;
            $statistic->setQuizz($this);
        }

        return $this;
    }

    public function removeStatistic(Statistic $statistic): self
    {
        if ($this->statistics->contains($statistic)) {
            $this->statistics->removeElement($statistic);
            // set the owning side to null (unless already changed)
            if ($statistic->getQuizz() === $this) {
                $statistic->setQuizz(null);
            }
        }

        return $this;
    }

    public function getAuthor(): ?User
    {
        return $this->author;
    }

    public function setAuthor(?User $author): self
    {
        $this->author = $author;

        return $this;
    }

    public function getCrew(): ?Crew
    {
        return $this->crew;
    }

    public function setCrew(?Crew $crew): self
    {
        $this->crew = $crew;

        return $this;
    }
    
    public function __toString()
    {
        return $this->title;
    }
}


Voici le Question Entity :
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\QuestionRepository")
 */
class Question
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $body;

    /**
     * @ORM\Column(type="string", length=128)
     */
    private $prop1;

    /**
     * @ORM\Column(type="string", length=128)
     */
    private $prop2;

    /**
     * @ORM\Column(type="string", length=128)
     */
    private $prop3;

    /**
     * @ORM\Column(type="string", length=128)
     */
    private $prop4;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $anecdote;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $source;

    /**
     * @ORM\Column(type="boolean")
     */
    private $errore;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Quizz", inversedBy="questions")
     * @ORM\JoinColumn(nullable=false)
     */
    private $quizz;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Level", inversedBy="questions")
     * @ORM\JoinColumn(nullable=false)
     */
    private $level;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getBody(): ?string
    {
        return $this->body;
    }

    public function setBody(string $body): self
    {
        $this->body = $body;

        return $this;
    }

    public function getProp1(): ?string
    {
        return $this->prop1;
    }

    public function setProp1(string $prop1): self
    {
        $this->prop1 = $prop1;

        return $this;
    }

    public function getProp2(): ?string
    {
        return $this->prop2;
    }

    public function setProp2(string $prop2): self
    {
        $this->prop2 = $prop2;

        return $this;
    }

    public function getProp3(): ?string
    {
        return $this->prop3;
    }

    public function setProp3(string $prop3): self
    {
        $this->prop3 = $prop3;

        return $this;
    }

    public function getProp4(): ?string
    {
        return $this->prop4;
    }

    public function setProp4(string $prop4): self
    {
        $this->prop4 = $prop4;

        return $this;
    }

    public function getAnecdote(): ?string
    {
        return $this->anecdote;
    }

    public function setAnecdote(?string $anecdote): self
    {
        $this->anecdote = $anecdote;

        return $this;
    }

    public function getSource(): ?string
    {
        return $this->source;
    }

    public function setSource(?string $source): self
    {
        $this->source = $source;

        return $this;
    }

    public function getErrore(): ?bool
    {
        return $this->errore;
    }

    public function setErrore(bool $errore): self
    {
        $this->errore = $errore;

        return $this;
    }

    public function getQuizz(): ?Quizz
    {
        return $this->quizz;
    }

    public function setQuizz(?Quizz $quizz): self
    {
        $this->quizz = $quizz;

        return $this;
    }

    public function getLevel(): ?Level
    {
        return $this->level;
    }

    public function setLevel(?Level $level): self
    {
        $this->level = $level;

        return $this;
    }
}


Au niveau de nos Wireframes, nous souhaitons avoir sur la même page un formulaire qui fait appel à l'entité Quizz et l'entité Question. Donc ce qui fait aussi alors logiquement 2 FormType.
Voici un exemple du rendu de ce que l'on souhaite :
upload/1536953740-72266-wireframe.png

J'ai testé donc avec un QuizzType et un QuestionType
Et du coup dans mon Controller j'ai ceci :
/**
     * @Route("/quizz/propose/nouveau", name="quizz_list_new")
     */
    public function new(Request $request, ObjectManager $manager)
    {
        $quizz = new Quizz();
        $question = new Question();

        $form1 = $this->createForm(QuizzType::class, $quizz);
        $form2 = $this->createForm(QuestionType::class, $question);


        $form1->handleRequest($request);
        $form2->handleRequest($request);

        dump($request);
        if ($form1->isSubmitted() && $form1->isValid()) {
            $manager->persist($quizz);
            $manager->persist($question);

            $manager->flush();

            return $this->redirectToRoute('quizz_list_show');
        }
        dump($request);

        return $this->render('quizz/new.html.twig', [
            'form1'=>$form1->createView(),
            'form2' => $form2->createView()
        ]);
    }


Lorsque je fait un dump($request), je ne récupère que les données de Question:
upload/1536954627-72266-request.png

D'autre part, l'autre problème qui se pose est comment répéter le formulaire Question 10 fois avec les propositions.
Concernant la vue j'ai mis les _formQuizz.html.twig et _formQuestion.html.twig à part et je les ai include dans le template new.html.twig.

On est bien sur des formulaires imbriqués mais comment faire ? Je suis un peu perdue car ce sont des choses qu'on n'a pas forcément vu en cours. Et j'ai beau chercher une solution, je ne trouve pas. C'est pour cette raison que mon dernier recours est le forum.

Si quelqu'un a une piste, une idée, un conseil, ça serait super ! Smiley smile
Merci
Modifié par Virginia (15 Sep 2018 - 14:03)
Bonjour,

Tu peux t'orienter sur les collections pour ce que tu cherches à faire: https://symfony.com/doc/current/form/form_collections.html

En gros voici le fonctionnement:
- un formulaire QuizzType possédant un "champ" questions de type CollectionType.
- un formulaire QuestionType possédant un champ question, proposition 1, proposition 2, proposition 3, proposition 4

Ce qui va donner quelque chose comme ça dans ton QuizzType:
            
->add(
  'questions',
  CollectionType::class,
  [
    'entry_type' => QuestionType::class, // le formulaire enfant qui doit être répété
    'allow_add' => false, // true si tu veux que l'utilisateur puisse en ajouter
    'allow_delete' => false, // true si tu veux que l'utilisateur puisse en supprimer
    'label' => 'Questions',
    'by_reference' => false, // voir  https://symfony.com/doc/current/reference/forms/types/collection.html#by-reference
 
  ]
);

Modifié par Raphi (17 Sep 2018 - 09:39)
Bonjour,
Merci pour votre réponse.
Oui après quelques recherches durant le weekend je suis bien tombée sur la CollectionType, donc je suis arrivée à imbriquer les deux entités Question et Quizz. Par contre ce que je n'arrive pas à faire c'est répéter 10 fois la partie Question avec les propositions, c'est-à-dire, ajouter d'autres champs dans le formulaire de la vue Twig.
J'ai testé une boucle for dans le Controller, ça ne fonctionne pas, j'ai aussi tester une boucle dans le formType, mais rien à faire.
Je tente de trouver une solution optimale mais rien à faire.
Si vous avez des idées encore elles seront bienvenue.
Et si tu met quelque chose comme ça dans ton controller ça donne quoi?

$quizz = new Quizz();

for ($i = 1; $i <= 10; $i++) {
    $quizz->addQuestion(new Question());
}

$form = $this->createForm(QuizzType::class, $quizz);

Modifié par Raphi (17 Sep 2018 - 10:33)
Alors j'ai tenté votre solution, ça ne fonctionne pas non plus, d'ailleurs cela ressemblait à la boucle que j'ai testé ce weekend. Du coup, changement de direction pour ne pas perdre de temps.
Dans le QuizzController finalement nous allons gérer d'abord l'ajout du Quizz de cette manière :

/**
     * @Route("/quizz/propose/nouveau", name="quizz_list_new")
     */
    public function new(Request $request, ObjectManager $manager)
    {
        $user = $this->getUser();
        $quizz = new Quizz();

        $form = $this->createForm(QuizzType::class, $quizz);

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            //? j'ajoute le User connecté comme auteur du quizz
            $quizz->setAuthor($user);
            // TODO qjouter un slugger
            $quizz->setSlug('test');
            // TODO comment géer la partie privée si l'utilisateur a plusieurs crew ?
            dump($user); 
            //$quizz->setCrew('user.crew')
            $manager->persist($quizz);
            $manager->flush();

            //? après la création du questionnaire j'oriente vers la  création des questions.
            return $this->redirectToRoute('questions_quizz', [
                'id' => $quizz->getId(),
                'quizz' => $quizz,
            ]);
        }

        return $this->render('quizz/new.html.twig', [
            'form' => $form->createView()
        ]);
    }


Puis ensuite les Questions comme ceci :
/**
     * @Route("/question/quizz/{id}/{nbr}", name="questions_quizz", defaults={"nbr"=0})
     */
    public function addQuestions(Request $request, ObjectManager $manager, $id, QuizzRepository $qr, $nbr) : Response
    {

        $question = new Question();
        //? je récupere l'id du quizz créer
        $quizz = $qr->findOneById($id);

        $form = $this->createForm(QuestionType::class, $question);
        $form->handleRequest($request);
        //? je crée une variable pour compter le nombre de question créées
        dump($nbr);
        $nbr ++;
        if ($form->isSubmitted() && $form->isValid()) { 

            $question->setBody('');
            $question->setProp1('');
            $question->setQuizz($quizz);
            $question->setErrore(0);
            dump($nbr);
            $manager->persist($question);
            
            $manager->flush();
            
            if ($nbr < 10) {

                return $this->render('quizz/newsQuestions.html.twig', [
                    'form' => $form->createView(),
                    'quizz' => $quizz,
                    'nbr'=>$nbr,
                ]);

            }

            return $this->redirectToRoute('quizz_list_sort', [
                'sort' => 'id'
            ]);
        }

        return $this->render('quizz/newsQuestions.html.twig', [
            'form' => $form->createView(),
            'quizz' => $quizz,
            'nbr' => $nbr,
        ]);

    }

Pour le moment on a bien tout d'abord l'ajout du Quizz et ensuite l'ajout des 10 Questions mais à chaque fois avec un rechargement de la page et les informations misent dans la Question 1 qui restent dans le formulaire lorsque qu'on passe à la suite.
La solution serait donc de gérer cela en Ajax pour avoir du moins un rendu fonctionnel.
Qu'en pensez-vous ?
Bon du coup ça m'a mis le doute, je viens de tester et je te confirme que l'approche avec une boucle dans le controller fonctionne.
À quoi ressemble ta vue?
Peux tu mettre un dump(form.questions) dans ta vue pour voir si ta collection est bien peuplée et transmise à la vue?
Bon même si tu t'orientes sur une solution plus "découpée", et que tu as peut-être déjà fait tout ça, voici comment tu peux isoler le problème en te posant ces questions:

1/ Est-ce que ma fonction addQuestion de mon entity fonctionne correctement?
2/ Est-ce que mon controller renseigne bien mon object quizz en lui ajoutant 10 questions vides?
3/ Est-ce que l'objet quizz est correctement transmis au formulaire?
4/ Est-ce que le formulaire possède encore ces infos lorsque je suis dans la vue?
5/ Est-ce que le code de ma vue boucle correctement sur les questions du formulaire pour les afficher toutes?

Une fois que tu as déterminé à quel niveau ça coince, c'est plus facile à débuguer.
Voila en espérant que ça puisse aider.
Pour le moment on tâtonne un peu, mais en découpant les tâches on récupère bien le Quizz et ensuite pour l'ajout des Questions on a bien l'Id du Quizz que l'on vient de renseigner. Voici le dump de $request de Quizz et ensuite de Question dans le Controller :
upload/1537203661-72266-requetes.png

Alors votre solution de boucle, j'ai du sans doute la mettre au mauvais endroit car de mon côté ça ne donnait rien de concluant.
Et dans les vues on a ça :
new.html.twig :
{% extends 'base.html.twig' %}

{% block title %}Nouveau Quizz
{% endblock %}

{% block body %}
    <div class="bubble col-8 offset-2">
        <h2 class="bubble-title">Propose ton nouveau Quizz</h2>
        <p>Propose ici ton nouveau Quizz.<br/>
            Tu dois saisir un titre de Quizz avec ses 10 Questions.<br/>
            Pour chaque Question, tu dois proposer 4 réponses aux choix.<br/>
            Tu pourras aussi partager une anecdote en lien avec la Question, et si le coeur t'en dis, tu pourras mettre un lien de la source où tu as trouvé cette anecdote.</p>
    </div>

    <div class="bubble col-8 offset-2">
        {{ include('quizz/_formNewQuizz.html.twig') }}
    </div>

{% endblock %}


Le include de la partie formQuizz :

{{ form_start(form)}}
{{ form_widget(form) }}
<button class="btn btn-success" type="submit">
    C'est parti pour ton nouveau Quizz !
</button {{ form_end(form) }}


La partie formQuestion :
{% extends 'base.html.twig' %}

{% block title %}Nouveau Quizz
{% endblock %}

{% block body %}
    <div class="bubble col-8 offset-2">
        <h2 class="bubble-title">Super aux Questions maintenant</h2>
        <p>Ton questionnaire se sent tout vide sans ses questions.<br/>
            <h4>titre du questionnaire :
                {{ quizz.title }}</h4>
        </div>

        <div class="bubble col-8 offset-2">
            <h3>Question N°{{ nbr }}</h3>
            {% dump('début') %}
            {% dump(nbr) %}
            <h5>courage plus que
                {{10 - nbr}}
                questions
            </h5>
            <p>
                {% if 3 > nbr %}
                    c'est un bon début !
                {% elseif 5 > nbr %}
                    c'est une moyenne
                {% elseif 7 > nbr %}
                    tu y es presque un dernier effort
                {% endif %}
            </p>
        </div>
        <div class="bubble col-8 offset-2">
            {{ form_start(form, {'action': path('questions_quizz', {'id': quizz.id, 'nbr' : nbr} )} ) }}
            {{ form_widget(form) }}
            <button class="btn btn-success" type="submit">
                {% dump(quizz) %}
                question suivante !
            </button {{ form_end(form) }} </div>
        {% endblock %}


Et côté Ajax on arrive à bloquer le submit mais après on bloque encore un peu, on est arrivé à un résultat où un coup le rechargement se faisait et un coup non :

var app = {

    init: function () {

        console.log('Hello Webpack Encore! Edit me in assets/js/app.js');

        $('#nextQuestion').on('submit', app.nextQuestion);
    },

    nextQuestion: function (event) {
        event.preventDefault();
        console.log('submit blocked');
        var dataToSend = $(this).serialize();

        var $form = $(this).closest('form');
        console.log(dataToSend);

        var jqXHR = $.ajax({
            url: '',
            method: 'POST',
            data: dataToSend,
            success: function () {

                console.log('ajax')
            }
        });
    }
}
$(app.init);


Je prends tout vos conseils et je vais voir ce qu'on peut faire avec tout ça.
Merci beaucoup pour votre disponibilité !
Bon alors on rame avec Ajax, le rechargement de la page se fait malgré tout, alors on a retesté l'idée de départ, c'est à dire tout mette en une seule page, et en effet hier je devais fatiguer et pas trop les yeux en face des trous, parce que ça fonctionne jusqu'à un certain point :
/**
     * @Route("/quizz/propose/nouveau", name="new_quizz")
     */
    public function new(Request $request, ObjectManager $manager)
    {
        $user = $this->getUser();
        $quizz = new Quizz();

        for ($i = 1; $i <= 10; $i++) {
            $quizz->addQuestion(new Question());
        }

        $form = $this->createForm(QuizzType::class, $quizz);

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            //? j'ajoute le User connecté comme auteur du quizz
            $quizz->setAuthor($user);
            // TODO ajouter un slugger
            $quizz->setSlug('test');
            // TODO comment gérer la partie privée si l'utilisateur a plusieurs crew ?
            dump($user);
            //  $quizz->setCrew('user.crew')
            $manager->persist($quizz);
            $manager->flush();
            dump($request);
            

            //? après la création du questionnaire j'oriente vers la  création des questions.
            return $this->redirectToRoute('questions_quizz', [
                'id' => $quizz->getId(),
                'quizz' => $quizz,
                'nbr' => 0,
            ]);
        }

Et là du coup en testant le submit pour voir ce qui se passe en BDD on a cette erreur :
upload/1537271043-72266-sql.png
Honnêtement on a tellement tester et essayer, qu'on est un peu perdu, par quel bout le prendre, est-ce qu'on part dans la bonne direction, etc ?
Ça me parait plus judicieux que vous soyez revenus à la solution de départ. Dans votre cas il vaut mieux avoir quelque chose de fonctionnel, sans ajax, plutôt qu'un mélange de techno pas totalement maîtrisé, pour tenter de palier aux difficultés rencontrées.
Il sera toujours possible de le faire évoluer avec de l'ajax mais au moins il y aura une solution fonctionnelle au départ.

L'erreur affichée est plutôt positive, le problème se situe maintenant à l'insertion en base, donc les données arrivent bien jusqu'au bout.
Par contre il indique que le champ errore (je viens de vérifier c'est de l'italien ça? ) est null, à quoi sert ce champ exactement?
Oui moi aussi ça me paraît plus judicieux, mais mon binôme n'a pas le même avis, il veut quand même se tenter à l'Ajax, on va négocier Smiley smile
Et il avait ajouter un "errore" qui en effet aurait du être "error" en anglais plutôt, pour qu'un utilisateur puisse avertir, s'il pense que la réponse est fause, le créateur du Quizz et les Admin. Mais en effet ça me fait une erreur dans le INSERT TO qui ne doit pas être à null.
Oui je me dis que c'est une bonne nouvelle qu'on arrive jusqu'à cette étape, mais concrètement le champ error dans l'ajout des questions on n'en a pas besoin finalement.
Malgré tout en mettant ce champ-là pour faire un autre test, j'ai la même erreur mais avec
An exception occurred while executing 'INSERT INTO question (body, prop1, prop2, prop3, prop4, anecdote, source, errore, quizz_id, level_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' with params [null, null, null, null, null, null, null, null, null, null]:

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'body' cannot be null

Modifié par Virginia (18 Sep 2018 - 16:32)
Disons que l'ajax ne devrais pas être utilisé pour pallier à un manque de maîtrise de symfony. Sinon ça ne fait que repousser le problème, mais ça ne fais pas monter en compétences. Surtout que ce type de problématiques vous les aurez très régulièrement.

Concernant ta problématique, tu es bien revenue sur un seul formulaire unique avec l'enregistrement du quizz et des questions en même temps c'est bien ça?
Ce que je ferais à ta place pour commencer par isoler le problème c'est de n'afficher qu'une seule question en modifiant la boucle du controller.
Ensuite tu renseignes ton formulaire et tu vois s'il passe une question vide ou pas.
Ce que tu peux faire dans ton controller, c'est mettre ceci avant ton persist et flush (à l'intérieur de la condition de soumission de ton formulaire).

var_dump($quizz->getQuestions());
exit();
De plus, as-tu modifié ton entity quizz pour ajouter le cascade persist comme ceci (indispensable pour sauvegarder automatiquement tes questions)?

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Question", mappedBy="quizz", cascade={"persist"}, orphanRemoval=true)
     */
    private $questions;
Oui je suis tout à fait d'accord avec vous, ce n'est pas la peine de se lancer dans des choses qu'on ne maîtrise pas forcément ou en tout cas où on est encore trop fébrile. Et puis c'est vrai qu'avec Symfony il y a tellement à faire que je me dit autant tester.
J'ai bien le cascade persist côté Quizz avec private Questions. Et en effet j'ai bien qu'un formulaire avec une partie Quizz et les 10 Questions.
Le dump d'une seule question pour isoler le problème n'est pas concluant tout est à null
upload/1537285615-72266-dump.png
Modifié par Virginia (18 Sep 2018 - 17:49)
Peux-tu mettre le code des entity Question et Quizz, le FormType Quizz et Question, et l'action du controller stp?
Ajax c'est assez simple avec Symfony, je te donne un exemple de mon projet.

J'ai dans mon fichier de routing.yml un path valide faisant référence à une action de mon controller. C'est ce path qui fera le lien entre mon action ajax et ma fonction php

ajaxSwitchEtatQds:
  path : /ajaxSwitchEtatQds
  defaults:
      _controller: AppBundle:Back:ajaxSwitchEtatQds

le premier ajaxSwitchEtatQds est mon "nom" de path que je peux appeler dans mon twig pour "génerer" automatiquement mon url ajax.

<input type="hidden" value="{{path("ajaxSwitchEtatQds")}}" id="path-validation-qds"/>

Ainsi, dans cette input j'aurai mon url "cacher" que je pourrais récupérer juste avant mon requête ajax.
Bien sûr dans mon controller j'ai ma function ajaxSwitchEtatQdsAction() (ne pas oublier "Action").

Ensuite, en Jquery
$.ajax({
			statusCode: {
				500: function() {
					alert("Une erreur 500 est survenue, contactez un administrateur.");
					deconnection();
				}
			},
			url: $("#path-validation-qds").val(  ),
			method : "post",
			data : {"dateDu":dateDu,"dateAu":dateAu,"codFour":codFour,"codPack":codPack},
			datatype: "html",
			success: function(result){
				$("#div-qds-table").css("display","block");
				$("#div-qds-table").html(result);
			},
			error: function(result){
				deconnection();
			}    
		});


Et voila.
Je créer mon <table> html mais dans ma fonction php, comme ça a chaque ajax uniquement mon tableau est mis à jours, et non la page en entier.
$("#div-qds-table").html(result); // result contient en faite que du html créer dans mon fonction php. c'est un string "<table><tbody>.....</table>" etc...
Modifié par JENCAL (19 Sep 2018 - 10:08)
Et puis si tu as une error de type "Ne doit pas être null", alors met dans ton controller une valeurs pas default à vide

/**
* @ORM\Column(type="string", length=255)
*/
private $body = "";


Au moins il sera JAMAIS null... mais si il est null alors ça sera la valeur par défault = "" qui sera prit.
Modifié par JENCAL (19 Sep 2018 - 10:07)
Merci pour vos réponses et votre entraide !
Alors on a fini par faire un point hier avec notre prof de Symfo. L'option d'avoir en premier l'ajout du Quizz avec ensuite par partie des 10 formulaires de Questions , avec la page qui se recharge ne le choque pas du tout. Ce qui manquerait ça serait juste un peu d'ergonomie, quelque chose de plus clair pour l'utilisateur afin qu'il sache où il en est dans l'ajout des questions. L'Ajax à la limite pour la V2 vue que la date des présentation des projets avance à grand pas et que, malgré notre bon rythme, il nous manque tout de même une partie importante, qui est de pouvoir jouer au Quizz.
Alors la soluce ou plutôt devrais-je dire l'astuce de contournement du problème, c'est dans la partie Question nous avons intégrer un aside avec une barre de progression ainsi qu'une reprise de la question postée juste avant.
Donc pour le QuizzController on a ça :
/**
     * @Route("/quizz/propose/nouveau", name="new_quizz")
     */
    public function new(Request $request, ObjectManager $manager)
    {
        $user = $this->getUser();
        $quizz = new Quizz();

        
        $form = $this->createForm(QuizzType::class, $quizz);
    
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            //? j'ajoute le User connecté comme auteur du quizz
            $quizz->setAuthor($user);
            // TODO ajouter un slugger
            $quizz->setSlug('test');
            // TODO comment géer la partie privée si l'utilisateur a plusieurs crew ?
            dump($user);
            //  $quizz->setCrew('user.crew')
            $manager->persist($quizz);
            $manager->flush();
            dump($request);
            

            //? après la création du questionnaire j'oriente vers la  création des questions.
            return $this->redirectToRoute('questions_quizz', [
                'id' => $quizz->getId(),
                'quizz' => $quizz,
                'nbr' => 0,
            ]);
        }

        return $this->render('quizz/new.html.twig', [
            'form' => $form->createView()
        ]);
    }

    /**
     * @Route("/question/quizz/{id}/{nbr}", name="questions_quizz", methods="POST|GET", defaults={"nbr"=0})
     */
    public function addQuestions(Request $request, ObjectManager $manager, $id, QuestionRepository $questionRepo ,QuizzRepository $qr, $nbr) : Response
    {

        $question = new Question();

        //? je récupere l'id du quizz créer
        $quizz = $qr->findOneById($id);

        dump($question);
        $form = $this->createForm(QuestionType::class, $question);
        $form->handleRequest($request);
        //? je crée une variable pour compter le nombre de question créées
        $nbr++;
        
        if ($form->isSubmitted() && $form->isValid()) {
            $question->setQuizz($quizz);
            $question->setErrore(0);
            $manager->persist($question);
            
            $manager->flush();
            
            $questions = $questionRepo->findBy(['quizz' => $id]);
            
            if ($nbr < 10) {
                $question = new Question();
                dump($questions);
                $form = $this->createForm(QuestionType::class, $question);
                return $this->render('quizz/newsQuestions.html.twig', [
                    'nbr' => $nbr,
                    'form' => $form->createView(),
                    'quizz' => $quizz,
                    'questions' => $questions,
                ]);
            }

            return $this->redirectToRoute('quizz_list_sort', [
                'sort ' => 'id'
            ]);
        }

        return $this->render('quizz/newsQuestions.html.twig', [
            'form' => $form->createView(),
            'quizz' => $quizz,
            'nbr' => $nbr,
        ]);
    }


Et nos vues Twig :
Partie NewQuizz avec le QuizzType

{% extends 'base.html.twig' %}
{% block new_quizz %}active{% endblock %}
{% block title %}Nouveau Quizz
{% endblock %}

{% block body %}
    <div class="col-8 offset-2">
        <div class="bubble">
            <h2 class="bubble-title">Propose ton Nouveau Quizz</h2>
            <p>Propose ici ton nouveau Quizz.<br/>
                Tu dois saisir un titre de Quizz avec ses 10 Questions.<br/>
                Pour chaque Question, tu dois proposer 1 bonne réponse ainsi que 3 fausses.<br/>
                Tu pourras aussi partager une anecdote en rapport avec la Question, et si le coeur t'en dis, tu pourras mettre un lien de la source où tu as trouvé cette anecdote.</p>
        </div>

        <div class="bubble">
            {{ include('quizz/_formNewQuizz.html.twig') }}
        </div>
    </div>
</div>
</main>
{% endblock %}

le include fait référence à ce fomulaire
{{ form_start(form)}}
{{ form_widget(form) }}

<button class="btn btn-success" type="submit">
    C'est parti pour ton nouveau Quizz !
</button>
{{ form_end(form) }}

Et pour les NewsQuestions avec les 10 avec le QuestionType
{% extends 'base.html.twig' %}
{% block new_quizz %}active{% endblock %}
{% block title %}Nouveau Quizz
{% endblock %}

{% block body %}
    <div class="row col-10 offset-1">
        {#
    <div class="bubble col-8 offset-2">
        TODO a mettre dans u flash message
        <h2 class="bubble-title">Super aux Questions maintenant</h2>
        <p>Ton questionnaire se sent tout vide sans ses questions.<br />
            <h4>titre du questionnaire :
                {{ quizz.title }}</h4>
    </div>
    #}

        <div class="bubble col-8" id="formDiv">
            {{ form_start(form, {'action': path('questions_quizz', {'id': quizz.id, 'nbr' : nbr } )} ) }}
            {{ form_widget(form) }}
            <button class="btn btn-success" type="submit">
                Question suivante !
            </button>
            {{ form_end(form) }}
        </div>

        <aside class="bubble ml-2 col-3">
            <h4>
                Tu es à la Question
                {{ nbr }}</span></h4>
        {#TODO a mettre dans un flash
        <h5>courage plus que
            {{10 - nbr}}
            questions
        </h5>
        <p>
            {% if 3 > nbr %}
            c'est un bon début !
            {% elseif 5 > nbr %}
            c'est une moyenne
            {% elseif 7 > nbr %}
            tu y es presque un dernier effort
            {% endif %}
        </p>
        #}
        <div class="progress">
            <div class="progress-bar bg-success" role="progressbar" aria-valuenow="{{ nbr -1 }}" style="width: {{ (nbr - 1) * 10 }}%" aria-valuemax="9">{{ nbr -1 }}
                / 10</div>
        </div>
        <div>
            {% if questions is defined %}
                {% for q in questions %}
                    <span class="text-left ">Question
                        {{ nbr }}:</span>
                    <p class="shortQuestion">{{ q.body }}</p>

                {% endfor %}
            {% endif %}
        </div>
    </aside>

</div>
{% endblock %}


Voilà en gros comment mettre de côté un problème, on y reviendra sûrement mais pour la V2 ou alors si avant le 3 octobre on a le temps, on vaincra Ajax Smiley smile

Encore merci de vous être penché sur cette problématique Smiley smile
C'est nickel, vous allez pouvoir avancer. Smiley smile
Bon courage pour la fin du projet et à bientôt pour de nouveaux challenges sur Symfony. Smiley lol
Pages :