Symfony 4 - Créer un blog pas à pas - Utiliser les formulaires

Par Nouvelle-Techno.fr le 5 septembre 2019 - Catégories : MVC PHP Tutoriel RGPD Symfony

Lire l'article sur le site d'origine

Dans un blog, les utilisateurs apprécient de pouvoir publier des commentaires.

Dans cet exemple, le choix est fait volontairement de ne pas demander aux visiteurs de s'inscrire, mais ce serait tout à fait possible.

Nous allons donc créer un formulaire en bas de chaque article pour permettre aux visiteurs de le commenter.

Il faudra donc prendre en compte plusieurs points :

Pour rappel, nous avons établi une relation un à plusieurs entre les tables "articles" et "commentaires".

Cette relation nous permet, lors de la récupération des données, de pouvoir également accéder à nos commentaires.

Nous avons créé, dans un article précédent, le contrôleur "ArticlesController". Dans ce contrôleur, la méthode "article" permet de récupérer un article et de l'afficher. La méthode actuelle est la suivante

/**
 * @Route("/{slug}", name="article")
*/
public function article($slug){
    // On récupère l'article correspondant au slug
    $article = $this->getDoctrine()->getRepository(Articles::class)->findOneBy(['slug' => $slug]);
    if(!$article){
        // Si aucun article n'est trouvé, nous créons une exception
        throw $this->createNotFoundException('L\'article n\'existe pas');
    }
    // Si l'article existe nous envoyons les données à la vue
    return $this->render('articles/article.html.twig', compact('article'));
}

C'est dans cette méthode que nous allons travailler pour y ajouter la gestion des commentaires, ceux-ci étant liés à un article.

Afficher les commentaires

Pour afficher les commentaires, la table "articles" étant liée à la table "commentaires", notre variable "article" va nous permettre d'y accéder facilement.

Pour commencer, étant donné que nous allons mettre en place une modération des commentaires, nous devons nous assurer de sélectionner les commentaires validés uniquement.

Nous avons créé l'entité "Commentaires" en ce sens, en y ajoutant une propriété "actif" qui est un booléen qui sera à 1 ou true quand le commentaire sera validé. Voici la totalité du code de l'entité "Commentaires"

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

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

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

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

    /**
     * @ORM\Column(type="text")
     */
    private $contenu;

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

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

    /**
     * @ORM\Column(type="datetime")
     */
    private $created_at;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Articles", inversedBy="commentaires")
     * @ORM\JoinColumn(nullable=false)
     */
    private $articles;

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

    public function getPseudo(): ?string
    {
        return $this->pseudo;
    }

    public function setPseudo(string $pseudo): self
    {
        $this->pseudo = $pseudo;

        return $this;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(string $email): self
    {
        $this->email = $email;

        return $this;
    }

    public function getContenu(): ?string
    {
        return $this->contenu;
    }

    public function setContenu(string $contenu): self
    {
        $this->contenu = $contenu;

        return $this;
    }

    public function getActif(): ?bool
    {
        return $this->actif;
    }

    public function setActif(bool $actif): self
    {
        $this->actif = $actif;

        return $this;
    }

    public function getRgpd(): ?bool
    {
        return $this->rgpd;
    }

    public function setRgpd(bool $rgpd): self
    {
        $this->rgpd = $rgpd;

        return $this;
    }

    public function getCreatedAt(): ?\DateTimeInterface
    {
        return $this->created_at;
    }

    public function setCreatedAt(\DateTimeInterface $created_at): self
    {
        $this->created_at = $created_at;

        return $this;
    }

    public function getArticles(): ?Articles
    {
        return $this->articles;
    }

    public function setArticles(?Articles $articles): self
    {
        $this->articles = $articles;

        return $this;
    }
}

Nous allons maintenant modifier la méthode "article" du contrôleur "ArticlesController" et y ajouter la requête Doctrine qui ira chercher les commentaires correspondant à l'article et actifs.

Il nous faudra donc utiliser la méthode "findBy" en lui passant les critères de sélection comme ci-dessous

$commentaires = $this->getDoctrine()->getRepository(Commentaires::class)->findBy([
    'articles' => $article,
    'actif' => 1
],['created_at' => 'desc']);

La variable "$commentaires" contiendra les détails des commentaires actifs de l'article.

Notre méthode "article" est maintenant comme suit

/**
 * @Route("/{slug}", name="article")
*/
public function article($slug){
    // On récupère l'article correspondant au slug
    $article = $this->getDoctrine()->getRepository(Articles::class)->findOneBy(['slug' => $slug]);

    // On récupère les commentaires actifs de l'article
    $commentaires = $this->getDoctrine()->getRepository(Commentaires::class)->findBy([
        'articles' => $article,
        'actif' => 1
    ],['created_at' => 'desc']);

    if(!$article){
        // Si aucun article n'est trouvé, nous créons une exception
        throw $this->createNotFoundException('L\'article n\'existe pas');
    }

    // Si l'article existe nous envoyons les données à la vue
    return $this->render('articles/article.html.twig', compact('article', 'commentaires'));
}

Afficher le nombre de commentaires

Nous allons commencer par afficher le nombre de commentaires correspondant à l'article. Nous allons donc ouvrir le fichier "templates/articles/article.html.twig" et y ajouter ce comptage.

Twig nous permet de connaître le nombre d'éléments dans une variable donnée par l'intermédiaire d'un filtre appelé "length" (longueur en Anglais).

Il nous suffira donc d'accéder à notre variable de cette façon

{{ commentaires|length }}

En plaçant cette expression directement dans notre fichier, voici le résultat

{% extends 'base.html.twig' %}

{% block title %}{{ article.titre }}{% endblock %}

{% block body %}
    <h1>{{ article.titre }}</h1>
    <p>{{ article.createdAt|date }} - Commentaires : {{ commentaires|length }}</p>
    <div>{{ article.contenu|raw }}</div>

    <p>Catégories : 
        {% for categorie in article.categories %}
            {{ categorie.nom }} 
        {% endfor %}
    </p>
{% endblock %}

Afficher le contenu des commentaires

Pour afficher les commentaires, nous allons faire une boucle sur notre variable "commentaires" et afficher l'auteur, la date et le texte. Nous utiliserons le "for...in..." pour ce faire, comme ceci

{% for commentaire in commentaires %}
    {# Se lit "Pour chaque commentaire dans commentaires" #}
{% endfor %}

A l'intérieur de la boucle, nous accédons aux information par l'intermédiaire de la variable "commentaire"

Si nous souhaitons donc afficher l'auteur, la date et le texte, respectivement "pseudo", "created_at" et "contenu" dans l'entité, nous écrirons ceci

{% extends 'base.html.twig' %}

{% block title %}{{ article.titre }}{% endblock %}

{% block body %}
    <h1>{{ article.titre }}</h1>
    <p>{{ article.createdAt|date }} - Commentaires : {{ commentaires|length }}</p>
    <div>{{ article.contenu|raw }}</div>

    <p>Catégories : 
        {% for categorie in article.categories %}
            {{ categorie.nom }} 
        {% endfor %}
    </p>

    <h2>Commentaires</h2>
    {% for commentaire in commentaires %}
        <p>Commentaire écrit par {{ commentaire.pseudo }} le {{ commentaire.createdAt|date }}</p>
        <p>{{ commentaire.contenu }}</p>
    {% endfor %}
{% endblock %}

Gérer l'absence de commentaire

Ceci étant dit, que se passe-t-il si il n'y a pas de commentaire ? La réponse est "RIEN"...

Celà peut être génant si vous souhaitez avoir un minimum de présentation.

Toutefois, Twig propose une option très intéressante.

Si aucun enregistrement n'est envoyé dans le "for...in...", nous pouvons utiliser le "else" afin de gérer le cas. Le code devient donc le suivant

{% for commentaire in commentaires %}
    <p>Commentaire écrit par {{ commentaire.pseudo }} le {{ commentaire.createdAt|date }}</p>
    <p>{{ commentaire.contenu }}</p>
{% else %}
    <p>Il n'y a pas encore de commentaire, publiez le premier !</p>
{% endfor %}

Ajouter un commentaire

Nous voici, enfin, au coeur du sujet de l'article. Comment ajouter un commentaire ???

Pour ce faire, nous aurons besoin d'un formulaire qui permettra au visiteur de préciser son pseudo, son e-mail, son commentaire, et également de donner son accord pour la collecte de ses données (RGPD).

Nous allons devoir suivre plusieurs étapes :

Créer le formulaire "Type"

Nous allons utiliser le terminal pour créer rapidement le squelette du formulaire et le relier à notre entité "Commentaires".

Ouvrez le terminal dans le dossier de votre projet et entrez la commande

php bin/console make:form

Il vous sera demandé d'entrer :

Une fois validé, vous aurez un nouveau fichier dans "src/Form" qui se nomme "CommentairesType.php" et ressemble à ceci

<?php

namespace App\Form;

use App\Entity\Commentaires;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class CommentairesType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('pseudo')
            ->add('email')
            ->add('contenu')
            ->add('actif')
            ->add('rgpd')
            ->add('created_at')
            ->add('articles')
        ;
    }

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

Dans ce fichier, la méthode "buildForm" nous permettra de personnaliser le formulaire, et la méthode "configureOptions" nous permet d'ajouter des options au formulaire. Ici, nous avons pour le moment le lien avec l'entité "Commentaires".

Personnaliser le formulaire

Nous pouvons personnaliser le formulaire en précisant des options pour chacun des champs.

A ce stade, les champs sont ajoutés mais nous n'avons rien personnalisé.

Nous allons effectuer les modifications suivantes :

Une fois les champs retirés, notre méthode "buildForm" ressemble à ceci

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('pseudo')
        ->add('email')
        ->add('contenu')
        ->add('rgpd')
    ;
}

Afin de préciser le type des champs, nous avons accès à des classes spécifiques. Pour définir un champ sur le type "email" nous utiliserons la classe "EmailType" comme ci-dessous

// Mettre au début du fichier
use Symfony\Component\Form\Extension\Core\Type\EmailType;

// Dans le builder
->add('email', EmailType::class)

Pour créer une case à cocher, c'est le même principe en utilisant "CheckboxType", mais nous préciserons également le texte à indiquer à l'utilisateur

// A ajouter en début de fichier
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;

// Dans le builder
->add('rgpd', CheckboxType::class, [
    'label' => 'J\'accepte que mes informations soient stockées dans la base de données de Mon Blog pour la gestion des commentaires. J\'ai bien noté qu\'en aucun cas ces données ne seront cédées à des tiers.'
])

Nous devons aussi ajouter un bouton pour soumettre le formulaire, nous utiliserons ici le "submitType".

Notre classe "CommentairesType" finalisée ressemble donc à ceci

<?php

namespace App\Form;

use App\Entity\Commentaires;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class CommentairesType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('pseudo')
            ->add('email', EmailType::class)
            ->add('contenu')
            ->add('rgpd', CheckboxType::class, [
                'label' => 'J\'accepte que mes informations soient stockées dans la base de données de Mon Blog pour la gestion des commentaires. J\'ai bien noté qu\'en aucun cas ces données ne seront cédées à des tiers.'
            ])
            ->add('envoyer', SubmitType::class)
        ;
    }

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

Relier le formulaire au contrôleur

Une fois le formulaire "type" créé, nous allons le relier à notre contrôleur afin de pouvoir l'utiliser dans notre vue.

Le formulaire étant lié à l'entité "Commentaires", nous allons commencer par instancier cette entité, puis créer le formulaire en lui passant notre instance.

Nous allons donc devoir ajouter les lignes suivantes dans notre contrôleur

// Ajouter au début du fichier pour que notre contrôleur puisse utiliser le formulaire
use App\Form\CommentairesType;

// Ajouter dans notre méthode article

// Nous créons l'instance de "Commentaires"
$commentaire = new Commentaires();

// Nous créons le formulaire en utilisant "CommentairesType" et on lui passe l'instance
$form = $this->createForm(CommentairesType::class, $commentaire);

Notre méthode "article" est maintenant un peu plus complexe. Vous noterez, dans le code ci-dessous, que nous ne pouvons plus utiliser le "compact" pour envoyer les données en raison de l'ajout du "createView" pour le formulaire.

/**
 * @Route("/{slug}", name="article")
*/
public function article($slug){
    // On récupère l'article correspondant au slug
    $article = $this->getDoctrine()->getRepository(Articles::class)->findOneBy(['slug' => $slug]);
    $commentaires = $this->getDoctrine()->getRepository(Commentaires::class)->findBy([
        'articles' => $article,
        'actif' => 1
    ],['created_at' => 'desc']);
    if(!$article){
        // Si aucun article n'est trouvé, nous créons une exception
        throw $this->createNotFoundException('L\'article n\'existe pas');
    }

    // Nous créons l'instance de "Commentaires"
    $commentaire = new Commentaires();

    // Nous créons le formulaire en utilisant "CommentairesType" et on lui passe l'instance
    $form = $this->createForm(CommentairesType::class, $commentaire);

    // Si l'article existe nous envoyons les données à la vue
    return $this->render('articles/article.html.twig', [
        'form' => $form->createView(),
        'article' => $article,
        'commentaires' => $commentaires,
    ]);
}

Afficher le formulaire dans la vue

Nous avons transmis le formulaire à notre vue sous le nom "form".

Nous pouvons l'afficher où nous le souhaitons en utilisant le code suivant

{{ form(form) }}

Cette simple ligne vous affiche le formulaire complet.

De nombreuses options existent et seront structurées comme les exemples ci-dessous

{{ form_start(form) }} {# Affichera la balise <form> #}

{{ form_end(form) }} {# Affichera la balise </form> #}

{{ form_row(form.pseudo) }} {# Affichera le label et le champ du pseudo #}

{{ form_label(form.pseudo) }} {# Affichera le label du pseudo #}

{{ form_widget(form.pseudo) }} {# Affichera le champ du pseudo #}

Notre vue contenant le formulaire est maintenant

{% extends 'base.html.twig' %}

{% block title %}{{ article.titre }}{% endblock %}

{% block body %}
    <h1>{{ article.titre }}</h1>
    <p>{{ article.createdAt|date }} - Commentaires : {{ commentaires|length }}</p>
    <div>{{ article.contenu|raw }}</div>

    <p>Catégories : 
        {% for categorie in article.categories %}
            {{ categorie.nom }} 
        {% endfor %}
    </p>

    <h2>Commentaires</h2>
    {% for commentaire in commentaires %}
        <p>Commentaire écrit par {{ commentaire.pseudo }} le {{ commentaire.createdAt|date }}</p>
        <p>{{ commentaire.contenu }}</p>
    {% else %}
        <p>Il n'y a pas encore de commentaire, publiez le premier !</p>
    {% endfor %}
    {{ form(form) }}
{% endblock %}

Traiter les données

Une fois le formulaire soumis par l'utilisateur, il devra être traîté.

Nous devrons, tout d'abord, préparer notre méthode "article" à recevoir les données en lui ajoutant en paramètre d'entrée l'objet "Request" de Symfony.

public function article($slug, Request $request){

Ceci étant fait, nous utiliserons la méthode "handleRequest" pour récupérer ces données

// Nous récupérons les données
$form->handleRequest($request);

Puis nous vérifions si le formulaire a été soumis et si les données sont valides

// Nous vérifions si le formulaire a été soumis et si les données sont valides
if ($form->isSubmitted() && $form->isValid()) {

}

Une fois ces vérifications effectuées, nous pouvons traiter les données.

Nous devrons suivre plusieurs étapes :

Définir les données "manquantes"

Nous avons deux données qui nous manquent pour écrire notre commentaire.

Nous allons utiliser les "setters" de notre entité pour les préparer.

Pour l'article, nous avons une variable "$article" qui le contient, nous écrirons donc

// Hydrate notre commentaire avec l'article
$commentaire->setArticles($article);

Pour la date, il nous suffit de prendre la date courante au moyen de l'objet "DateTime"

// Hydrate notre commentaire avec la date et l'heure courants
$commentaire->setCreatedAt(new \DateTime('now'));

Hydrater notre objet

Nous allons maintenant hydrater notre instance "commentaire" avec les données du formulaire

$doctrine = $this->getDoctrine()->getManager();

// On hydrate notre instance $commentaire
$doctrine->persist($commentaire);

Ecrire les données dans la base

Dernière étape, nous enregistrons les données dans la base

// On écrit en base de données
$doctrine->flush();

Voilà notre contrôleur terminé, le code complet de la méthode "article" est le suivant

/**
 * @Route("/{slug}", name="article")
*/
public function article($slug, Request $request){
    // On récupère l'article correspondant au slug
    $article = $this->getDoctrine()->getRepository(Articles::class)->findOneBy(['slug' => $slug]);
    $commentaires = $this->getDoctrine()->getRepository(Commentaires::class)->findBy([
        'articles' => $article,
        'actif' => 1
    ],['created_at' => 'desc']);
    if(!$article){
        // Si aucun article n'est trouvé, nous créons une exception
        throw $this->createNotFoundException('L\'article n\'existe pas');
    }

    // Nous créons l'instance de "Commentaires"
    $commentaire = new Commentaires();

    // Nous créons le formulaire en utilisant "CommentairesType" et on lui passe l'instance
    $form = $this->createForm(CommentairesType::class, $commentaire);

    // Nous récupérons les données
    $form->handleRequest($request);

    // Nous vérifions si le formulaire a été soumis et si les données sont valides
    if ($form->isSubmitted() && $form->isValid()) {
        // Hydrate notre commentaire avec l'article
        $commentaire->setArticles($article);

        // Hydrate notre commentaire avec la date et l'heure courants
        $commentaire->setCreatedAt(new \DateTime('now'));

        $doctrine = $this->getDoctrine()->getManager();

        // On hydrate notre instance $commentaire
        $doctrine->persist($commentaire);

        // On écrit en base de données
        $doctrine->flush();
    }
    // Si l'article existe nous envoyons les données à la vue
    return $this->render('articles/article.html.twig', [
        'form' => $form->createView(),
        'article' => $article,
        'commentaires' => $commentaires,
    ]);
}

Et voilà qui termine cet article

#Tutoriel #Controllers #Framework #MVC #PHP #RGPD #Views #Symfony #Formulaires