8768 sujets

Développement web côté serveur, CMS

Bonjour,
Je reviens maintenant avec un autre problème au niveau de mes formulaires et 3 entités.

J'ai une entité User :
<?php
 
namespace App\Entity;
 
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;
 
/**
 * @ORM\Table(name="app_user")
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 */
class User implements UserInterface, \Serializable
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;
    /**
     * @ORM\Column(type="string", length=64)
     */
    private $username;
 
    /**
     * @ORM\Column(type="string", length=255)
     */
    private $email;
 
    /**
     * @ORM\Column(type="string", length=64)
     */
    private $password;
 
    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Role", inversedBy="users")
     */
    private $role;
 
    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Response", mappedBy="users")
     */
    private $responses;
 
    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Question", mappedBy="user")
     */
    private $questions;
 
    public function __construct()
    {
        $this->responses = new ArrayCollection();
        $this->questions = new ArrayCollection();
    }
 
    public function __toString()
    {
        return $this->getUsername();
    }
 
    public function getId(): ?int
    {
        return $this->id;
    }
 
    public function getUsername(): ?string
    {
        return $this->username;
    }
 
    public function getSalt()
    {
        // you *may* need a real salt depending on your encoder
        // see section on salt below
        return null;
    }
 
    public function getPassword(): ?string
    {
        return $this->password;
    }
 
    public function getRoles()
    {
        return [$this->role->getCode()];
    }
 
    public function eraseCredentials()
    {
    }
 
    public function serialize()
    {
        return serialize(array(
            $this->id,
            $this->username,
            $this->password,
            // see section on salt below
            // $this->salt,
        ));
    }
 
    public function unserialize($serialized)
    {
        list(
            $this->id,
            $this->username,
            $this->password,
            // see section on salt below
            // $this->salt
        ) = unserialize($serialized, array('allowed_classes' => false));
    }
 
    public function setUsername(string $username): self
    {
        $this->username = $username;
 
        return $this;
    }
 
    public function setPassword($password)
    {
        $this->password = $password;
        return $this;
    }
 
    public function getEmail(): ?string
    {
        return $this->email;
    }
 
    public function setEmail(string $email): self
    {
        $this->email = $email;
 
        return $this;
    }
 
    public function getRole(): ?Role
    {
        return $this->role;
    }
    public function setRole(?Role $role): self
    {
        $this->role = $role;
        return $this;
    }
 
    /**
     * @return Collection|Response[]
     */
    public function getResponses(): Collection
    {
        return $this->responses;
    }
 
    public function addResponse(Response $response): self
    {
        if (!$this->responses->contains($response)) {
            $this->responses[] = $response;
            $response->setUsers($this);
        }
 
        return $this;
    }
 
    public function removeResponse(Response $response): self
    {
        if ($this->responses->contains($response)) {
            $this->responses->removeElement($response);
            // set the owning side to null (unless already changed)
            if ($response->getUsers() === $this) {
                $response->setUsers(null);
            }
        }
 
        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->setUser($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->getUser() === $this) {
                $question->setUser(null);
            }
        }
 
        return $this;
    }
}


J'ai une autre entité Question :
<?php
 
namespace App\Entity;
 
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
 
/**
 * @ORM\Entity(repositoryClass="App\Repository\QuestionRepository")
 */
class Question
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;
 
    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\Length(min=10, max=255)
     */
    private $titleQuestion;
 
    /**
     * @ORM\Column(type="text")
     */
    private $description;
 
    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Response", mappedBy="question", cascade={"persist"})
     */
    private $responses;
 
    /**
     * @ORM\ManyToMany(targetEntity="App\Entity\Tag", inversedBy="questions")
     */
    private $tags;
 
    /**
     * @ORM\Column(type="datetime")
     */
    private $createdAt;
 
    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="questions", cascade={"persist"})
     */
    private $user;
 
 
    public function __construct()
    {
        $this->responses = new ArrayCollection();
        $this->tags = new ArrayCollection();
    }
 
    public function __toString()
    {
        return $this->getTitleQuestion();
    }
 
    public function getId(): ?int
    {
        return $this->id;
    }
 
    public function getTitleQuestion(): ?string
    {
        return $this->titleQuestion;
    }
 
    public function setTitleQuestion(string $titleQuestion): self
    {
        $this->titleQuestion = $titleQuestion;
 
        return $this;
    }
 
    public function getDescription(): ?string
    {
        return $this->description;
    }
 
    public function setDescription(string $description): self
    {
        $this->description = $description;
 
        return $this;
    }
 
    /**
     * @return Collection|Response[]
     */
    public function getResponses(): Collection
    {
        return $this->responses;
    }
 
    public function addResponse(Response $response): self
    {
        if (!$this->responses->contains($response)) {
            $this->responses[] = $response;
            $response->setQuestion($this);
        }
 
        return $this;
    }
 
    public function removeResponse(Response $response): self
    {
        if ($this->responses->contains($response)) {
            $this->responses->removeElement($response);
            // set the owning side to null (unless already changed)
            if ($response->getQuestion() === $this) {
                $response->setQuestion(null);
            }
        }
 
        return $this;
    }
 
    /**
     * @return Collection|Tag[]
     */
    public function getTags(): Collection
    {
        return $this->tags;
    }
 
    public function addTag(Tag $tag): self
    {
        if (!$this->tags->contains($tag)) {
            $this->tags[] = $tag;
        }
 
        return $this;
    }
 
    public function removeTag(Tag $tag): self
    {
        if ($this->tags->contains($tag)) {
            $this->tags->removeElement($tag);
        }
 
        return $this;
    }
 
    public function getCreatedAt(): ?\DateTimeInterface
    {
        return $this->createdAt;
    }
 
    public function setCreatedAt(\DateTimeInterface $createdAt): self
    {
        $this->createdAt = $createdAt;
 
        return $this;
    }
 
    public function getUser(): ?User
    {
        return $this->user;
    }
 
    public function setUser(?User $user): self
    {
        $this->user = $user;
 
        return $this;
    }
}


Et pour finir une entité Response :
<?php
 
namespace App\Entity;
 
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
 
/**
 * @ORM\Entity(repositoryClass="App\Repository\ResponseRepository")
 */
class Response
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;
 
    /**
     * @ORM\Column(type="text", nullable=true)
     */
    private $answer;
 
    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Question", inversedBy="responses")
     * @ORM\JoinColumn(nullable=false)
     */
    private $question;
 
    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="responses", cascade={"persist"})
     */
    private $users;
 
    /**
     * @ORM\Column(type="datetime")
     */
    private $createdAt;
 
    public function getId(): ?int
    {
        return $this->id;
    }
 
    public function getAnswer(): ?string
    {
        return $this->answer;
    }
 
    public function setAnswer(?string $answer): self
    {
        $this->answer = $answer;
 
        return $this;
    }
 
    public function getQuestion(): ?Question
    {
        return $this->question;
    }
 
    public function setQuestion(?Question $question): self
    {
        $this->question = $question;
 
        return $this;
    }
 
    public function getUsers(): ?User
    {
        return $this->users;
    }
 
    public function setUsers(?User $users): self
    {
        $this->users = $users;
 
        return $this;
    }
 
    public function getCreatedAt(): ?\DateTimeInterface
    {
        return $this->createdAt;
    }
 
    public function setCreatedAt(\DateTimeInterface $createdAt): self
    {
        $this->createdAt = $createdAt;
 
        return $this;
    }
}


J'ai crée dans mon controller Question une page pour ajouter une question :
/**
    * @Route("/question/new", name="question_new", methods="GET|POST")
    */
    public function formQuestion(Request $request)
    {
        $question = new Question();
         
        $form = $this->createForm(QuestionType::class, $question);
         
        $form->handleRequest($request);
         
        if ($form->isSubmitted() && $form->isValid()) {
            $manager = $this->getDoctrine()->getManager();
 
            $manager->persist($question);
            $manager->flush();
 
            return $this->redirectToRoute('question_show', ['id'=> $question->getId()]);
        }
 
        return $this->render('question/new.html.twig', [
            'formQuestion'=>$form->createView()
        ]);
    }


Voici le formType :
namespace App\Form;
 
use App\Entity\Question;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
 
class QuestionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('titleQuestion', TextType::class, [
                'label'=>'Titre de la question',
                'attr'=>['placeholder'=>'Titre de la question']
            ])
            ->add('description', TextareaType::class, [
                'label'=>'Description',
                'attr'=>['placeholder'=>'Ajouter un descriptif à votre question']
            ])
            ->add('tags')
            ->add('user', TextType::class, [
                'label' => 'Votre nom',
                'attr'=>['placeholder'=> 'Nom'],
                'constraints'=>[new NotBlank()]
            ]);
    }
 
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Question::class,
        ]);
    }
}


Ma vue twig :
{% extends 'base.html.twig' %}
 
{% form_theme formQuestion 'bootstrap_4_layout.html.twig' %}
 
{% block title %}Créer une nouvelle question{% endblock %}
 
{% block body %}
    <h2>Posez votre question</h2>
 
    {{ form_start(formQuestion, {'attr': {'novalidate': 'novalidate'}})}}
 
    {{ form_widget(formQuestion) }}
 
    <button type="submit" class="btn btn-success">Ajouter la question</button>
    {{ form_end(formQuestion)}}
 
{% endblock %}


Et l'autre problème se situe dans mes réponses, voici la route dans le controller Question :
/**
     * @Route("/question/{id}", name="question_show")
     */
    public function show(Question $question, Request $request)
    {
        $response = new Response();
 
        $form = $this->createForm(ResponseType::class, $response);
         
        $form->handleRequest($request);
     
        if ($form->isSubmitted() && $form->isValid()) {
            $manager = $this->getDoctrine()->getManager();
 
            $manager->persist($response);
            $manager->flush();
 
            return $this->redirectToRoute('question_show', ['id'=> $question->getId()]);
        }
 
        return $this->render('question/show.html.twig', [
            'question' => $question,
            'formResponse'=>$form->createView(),
             
        ]);
    }


Le formType des Response :

<?php
 
namespace App\Form;
 
use App\Entity\Response;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
 
class ResponseType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('answer', TextareaType::class, [
                'label'=>'Votre réponse',
                'attr'=>['placeholder'=>'Veuillez saisir votre réponse']
            ])
            ->add('users', TextType::class, [
                'label'=>'Votre nom',
                'attr'=>['placeholder'=>'Nom']
            ])
        ;
    }
 
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Response::class,
        ]);
    }
}


L'affichage est bon tout marche bien jusqu'au moment où j'envoi le fomulaire, j'ai une erreur en récupérant mon User, que cela soit en envoyant une nouvelle question ou alors en répondant à une question existante.
upload/1535900401-72266-erreur.png

Je tente plusieurs solutions mais rien n'y fait, je ne vois pas comment m'en sortir, par contre je vois bien en gros où peut se situer le problème.

Pourtant cela paraît simple mais je tourne en rond.

Si vous avez des idées, je suis preneuse.

Merci !
Après avoir fait divers tests, sur mon QuestionType :
public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('titleQuestion', TextType::class, [
                'label'=>'Titre de la question',
                'attr'=>['placeholder'=>'Titre de la question']
            ])
            ->add('description', TextareaType::class, [
                'label'=>'Description',
                'attr'=>['placeholder'=>'Ajouter un descriptif à votre question']
            ])
            ->add('tags')
            ->add('user');
    }


J'ai bien mon User mais dans un ChoiceType et ce n'est pas ce que je veux, je souhaite que la personne puisse indiqué son nom "username".

Avez-vous une idée avec ce complément d'information que je donne ?
Bonjour,

Le comportement suivant est normal:
a écrit :
J'ai bien mon User mais dans un ChoiceType et ce n'est pas ce que je veux

Pour ton entity Question tu as déclaré ceci:
    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="questions", cascade={"persist"})
     */
    private $user;

Ce qui implique qu'il faudra dans ton formulaire, passer un objet User et non pas un string comme tu tente de le faire avec ça dans ton form:
->add('user', TextType::class, [
                'label' => 'Votre nom',
                'attr'=>['placeholder'=> 'Nom'],
                'constraints'=>[new NotBlank()]
            ])


a écrit :
je souhaite que la personne puisse indiqué son nom "username".

Quel est le but exactement? Tu veux enregistrer un nouvel utilisateur en même temps que tu soumet ce formulaire?
Modifié par Raphi (03 Sep 2018 - 09:45)
Oui j'ai bien compris que le comportement que j'ai est tout à fait normal mais je souhaiterais résoudre le problème, c'est-à-dire que, je souhaite qu'un utilisateur déjà en BDD puisse poster sa nouvelle question et que les utilisateurs déjà connectés puissent y répondre.
Par exemple mon formulaire tout simple d'inscription marche très bien.
Peut-être devrais-je gérer avant ça les users ?
Je me suis dit que j'allais gérer ça après avec les roles : admin et user.
Modifié par Virginia (03 Sep 2018 - 11:22)
Pas sur d'avoir compris ta demande.
En gros, tu voudrais que l'utilisateur soit repris automatiquement dans ton objet en fonction de celui qui est connecté?
Si c'est cela, tu n'as pas besoin d'indiquer le champ user dans ton Form, tu peux donc le retirer, ainsi que dans ta vue twig.

Ensuite tu peux modifier ton objet $question dans ton controller en lui passant l'utilisateur courant comme ceci:
$question = new Question();
$question->setUser($this->getUser());
...

Modifié par Raphi (03 Sep 2018 - 15:44)
a écrit :
Peut-être devrais-je gérer avant ça les users ?

J'avais pas vu ta question. Ah oui il vaut mieux commencer par gérer les utilisateurs (création, modification) et leur permettre de se connecter/déconnecter. Ensuite, tu n'autorises un utilisateur à poser une question ou répondre que s'il est connecté.

a écrit :
Je me suis dit que j'allais gérer ça après avec les roles : admin et user.

Tout dépend ce que tu comptes faire exactement?

Pour ma solution précédente, tu peux aussi passer par un listener au lieu d'écrire du code dans ton controller. Comme expliqué ici.
Pour ce faire il te suffit d'effectuer ce traitement sur l'événement prePersist et preUpdate.
Ça te permet de définir l'utilisateur de ta question juste avant de sauvegarder ta question. Et cela de manière transparente. Tu peux étendre très facilement cette logique à tes réponses sans avoir à dupliquer ton code, puisque tu ajouteras simplement une condition dans ton listener. Bon après ça rajoute une petite couche de complexité pas indispensable dans ton cas, mais c'est une bonne pratique. Et tu en aura besoin en progressant sur Symfony. Smiley smile
Modifié par Raphi (03 Sep 2018 - 16:08)
Merci Raphi d'avoir pris le temps de vous pencher sur mon problème. Et du coup entre vos indications et mes essais j'ai résolu le souci.
Donc en effet, j'ai mis de côté les Questions et les Réponses, pour plutôt gérerr les simples visiteurs et les utilisateurs identifiés qui peuvent poster de nouvelles questions et répondre aux questions.
Une fois cela effectuer, j'ai dans mon QuestionController l'ajout d'une nouvelle question :

/**
    * @Route("/question/new", name="question_new", methods="GET|POST")
    */
    public function formQuestion(Request $request, ObjectManager $manager)
    {
        $question = new Question();
        $question->setCreatedAt(new \DateTime('now'));

        $form = $this->createForm(QuestionType::class, $question);
        
        $form->handleRequest($request);
        
        if ($form->isSubmitted() && $form->isValid()) {
            $question->setUser($this->getUser());

            $manager->persist($question);
            $manager->flush();

            return $this->redirectToRoute('question_show', ['id'=> $question->getId()]);
        }

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


L'ajout des réponses :
/**
     * @Route("/question/{id}", name="question_show")
     */
    public function show(Question $question, Request $request, ObjectManager $manager)
    {
        $response = new Response();
        
        $form = $this->createForm(ResponseType::class, $response);

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $response->setCreatedAt(new \DateTime())
                    ->setQuestion($question)
                    ->setUsers($this->getUser());

            $manager->persist($response);
            $manager->flush();
            return $this->redirectToRoute('question_show', ['id' => $question->getId()]);
        }
        return $this->render('question/show.html.twig', [
            'question' => $question,
            'formResponse' => $form->createView(),

        ]);
    }


Et dans les FormType, comme les utilisateurs sont identifiés pas besoin en effet de mettre
->add('user');
.
Ce qui donne donc pour le QuestionType :
<?php

namespace App\Form;

use App\Entity\Question;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;

class QuestionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('titleQuestion', TextType::class, [
                'label'=>'Titre de la question',
                'attr'=>['placeholder'=>'Titre de la question']
            ])
            ->add('description', TextareaType::class, [
                'label'=>'Description',
                'attr'=>['placeholder'=>'Ajouter un descriptif à votre question']
            ])
            ->add('tags');
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Question::class,
        ]);
    }
}


Et ResponseType :
<?php

namespace App\Form;

use App\Entity\Response;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;

class ResponseType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('answer', TextareaType::class, [
                'label'=>'Votre réponse',
                'attr'=>['placeholder'=>'Veuillez saisir votre réponse']
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Response::class,
        ]);
    }
}


Au niveau de ma vue twig tout est nickel, une page à part avec le formulaire pour l'ajout d'une nouvelle question et dans la page d'une question en particulier, un formulaire pour l'ajout d'une réponse.

Ouf problème résolu, merci encore et si ça peut servir pour d'autres tant mieux.
Merci beaucoup !
Je fini ce mini projet de fin de spécialité Symfony et j'entame dans 2 jours un mois de projet complet en binôme, de fin de formation Smiley smile
J'ai vu que tu faisais ta formation chez Oclock (oui je suis curieux Smiley lol ). J'étais déjà tombé sur leur site. Je trouve que leur programme et leur concept a l'air vraiment sympa et qualitatif !
À l'occasion si t'as envie de partager un retour d'expérience sur le forum n'hésites pas, ça pourrait en intéresser certains qui cherche à se réorienter ou à se former.