Cours - Symfony - Chapitre 05 - Enregistrer les contacts dans une base de données

SOMMAIRE



Introduction


L’objectif de ce chapitre est d’enregistrer dans une table, les données du formulaire de contact lorsqu’il est utilisé. Ainsi, si l’administrateur du site ne reçoit pas l’email, il peut toujours le lire dans son interface utilisateur.


Nous allons voir que Symfony possède un ORM (Object Relational Mapping) qui se nomme « Doctrine ». Cet outil va faire le lien entre votre code et la base de données.


Avant de commencer, nous allons regarder ce que propose notre serveur pour apprendre les bases de données. Le Système de Gestion de Base de Données Relationnelles (SGBDR) « Mariadb », un dérivé de « Mysql » est installé.


Configuration de Mariadb


Allez sur le Nuage-Pédagogique, précisément sur votre profil et cliquez sur l’icône suivante :




Vous arrivez sur « PhpMyAdmin », une interface écrite en « PHP « qui permet de créer et de gérer des bases des données dans « Mysql/Mariadb ». « Doctrine » va le faire pour nous, mais nous pourrons vérifier que tout se passe bien avec « PhpMyAdmin ».





Connectez-vous avec vos identifiants que vous trouverez dans votre profil :




Puis vous voyez cette interface :




Vous constatez qu’il existe déjà 2 bases de données. Une (dblogin suivi d’un numéro) qui a été créée pour vous automatiquement par le nuage afin de vous dépanner au besoin.

La 2ème, « information_schema » regroupe des informations sur le serveur de bases de données, les privilèges, etc.


Donner des privilèges dans Mariadb


Par défaut, vous n’avez pas la possibilité de créer d’autres bases de données. En effet, le nuage existe afin que vous appreniez à faire des choses, ainsi, vous allez donner à votre login tous les droits.


Accédez à VSCODE à partir de votre profil, allez dans le menu puis cliquez sur « Terminal », « New Terminal ».



Par défaut, vous êtes le super utilisateur « root ». Accédez à Mysql avec la commande suivante :


mysql




Vérifiez que votre login existe bien dans votre « Mysql » en tapant la commande suivante :


select host, user from mysql.user;




Nous pouvons voir que votre login existe déjà uniquement s’il se connecte à partir de votre serveur. (localhost). Nous voyons qu’il y a également l’utilisateur « root ».


Donnez tous les droits à votre utilisateur avec la commande suivante :


grant all privileges on *.* to 'login4400'@'localhost';


Remarque : Vous donnez tous les droits à votre utilisateur. Pour une machine de développement c’est autorisé, mais ne faites jamais cela sur une machine en production (serveur accessible par vos visiteurs).


Nous rechargeons les droits afin que nos droits soient prise en compte avec la commande :


flush privileges ;


Vous avez maintenant la possibilité de créer d’autres bases de données. Il faut vous déconnecter et vous reconnecter sur PhpMyAdmin pour le vérifier.









Configuration du projet Symfony pour Doctrine



Nous allons configurer notre projet pour utiliser « Doctrine ».




Avec le terminal de VSCODE, mettez-vous sur votre compte login.


su login4400


Puis mettez-vous dans votre projet avec la commande :


cd share


Installez maintenant les modules nécessaires :


composer require symfony/orm-pack
composer require --dev symfony/maker-bundle


Il est important de connaitre votre version de Mariadb, taoez dans le terminal :


mariadb --version




La valeur qu’il faut retenir est celle-ci : 10.3.29


Nous allons maintenant utiliser cette valeur dans le fichier de « Symfony » qui regroupe les variables d’environnement.


Dans .env, nous allons commenter la ligne qui commence par « DATABASE_URL="postgresql », car le SGBDR que nous utiliserons est Mysql/Mariadb. Mettez un # devant.

Nous retirons le # de ligne qui commence par « DATABASE_URL="mysql: » pour l’activer.

Nous configurons cette ligne en y mettant nos identifiants. Il s’agit de mettre notre login et le mot de passe associé. Nous allons donner un nom à notre base de données. Cela sera « dbshare ».

Enfin, nous allons spécifier la version de Mariadb.

Voici le fichier que vous devez obtenir :


# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
#  * .env                contains default values for the environment variables needed by the app
#  * .env.local          uncommitted file with local overrides
#  * .env.$APP_ENV       committed environment-specific defaults
#  * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration

###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=34f7c185b6850bdf344944098254b129
###< symfony/framework-bundle ###


###> symfony/mailer ###
MAILER_DSN=smtp://localhost?verify_peer=0
###< symfony/mailer ###

###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
#
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
DATABASE_URL="mysql://login4400:motDePasse@127.0.0.1:3306/dbshare?serverVersion=mariadb-10.3.29"
#DATABASE_URL="postgresql://symfony:ChangeMe@127.0.0.1:5432/app?serverVersion=13&charset=utf8"
###< doctrine/doctrine-bundle ###


Création de la table « Contact »


Maintenant que « Doctrine » est configuré, nous allons lui demander de créer la base de données « dbshare » :


php bin/console doctrine:database:create


Vous pouvez vérifier dans PhpMyAdmin, que votre base de données existe (si ce n’est pas le cas, rafraichissez la page).






Nous souhaitons maintenant créer notre table « Contact » qui nous permettra de sauvegarder toutes les prises de contact réalisées sur le site.



Voici sa structure : (identique aux noms des composants du formulaire « Contact »)


Nom du champ

Type

Longueur

Commentaire

nom

string

30

contient le nom de la personne qui enverra un message

sujet

string

100

contient le sujet du message

email

string

100

contient l’email

message

text


contient le message qui peut être long et sur plusieurs lignes.


Pour créer cette table, il y a plusieurs étapes.


  • La première est d’utiliser la commande suivante :

    php bin/console make:entity

  • Saisir « Contact ». (avec la 1re lettre en majuscule, car il s’agit d’une classe)

  • La commande nous demande le nom d’une propriété. Nous saisissons « nom » (la 1re lettre en minuscule, car il s’agit d’un attribut de la classe).

  • Nous devons maintenant choisir le type, par défaut, la console nous propose « string » et cela nous convient.

  • Nous mettons maintenant la longueur, ici, nous choisissons une longueur de 30.

  • La question suivante demande si le nom peut être nul dans la table. Une prise de contact sans saisir un nom n’a pas de sens. Nous répondons « non ».

  • La commande va maintenant recommencer pour créer la 2ème propriété, « sujet ».

  • En vous appuyant sur le tableau décrivant la structure de la table « Contact », créez les autres propriétés. Lorsque vous aurez terminé, appuyez sur « entrer » au moment où le programme vous demandera le nom d’une nouvelle propriété.


Une fois terminée, la commande crée 2 fichiers.

  • Contact.php dans « src/Entity/ ».

  • ContactRepository.php «src/Repository/ ».


Contenu du fichier « Contact.php » 


Voici le contenu de « Contact.php » :


<?php

namespace App\Entity;

use App\Repository\ContactRepository;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass=ContactRepository::class)
 */
class Contact
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

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

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

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

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

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

    public function getNom(): ?string
    {
        return $this->nom;
    }

    public function setNom(string $nom): self
    {
        $this->nom = $nom;

        return $this;
    }

    public function getSujet(): ?string
    {
        return $this->sujet;
    }

    public function setSujet(string $sujet): self
    {
        $this->sujet = $sujet;

        return $this;
    }

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

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

        return $this;
    }

    public function getMessage(): ?string
    {
        return $this->message;
    }

    public function setMessage(string $message): self
    {
        $this->message = $message;

        return $this;
    }
}


Remarques :


La commande « make:entity » a créé un fichier pour l’entité « Contact ». Il s’agit d’une classe qui comprend tous les attributs et leurs accesseurs. (get et set). Elle a géré la création de l’identifiant « id » qui s’incrémente tout seul lors de l’insertion dans la base de données.


/**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */



Chaque attribut est relié à Doctrine à l’aide d’annotations que nous trouvons au-dessus de chaque propriété. Ce ne sont pas des commentaires et sont bel et bien interprétés par doctrine.


Chaque attribut est mappé, c’est-à-dire que l’objet est relié à la base de données. En manipulant l’objet, vous agissez facilement sur la base de données.


Contenu du fichier « ContactRepository.php » 


Voici le contenu du fichier « ContactRepository.php » :


<?php

namespace App\Repository;

use App\Entity\Contact;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
 * @method Contact|null find($id, $lockMode = null, $lockVersion = null)
 * @method Contact|null findOneBy(array $criteria, array $orderBy = null)
 * @method Contact[]    findAll()
 * @method Contact[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class ContactRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Contact::class);
    }

    // /**
    //  * @return Contact[] Returns an array of Contact objects
    //  */
    /*
    public function findByExampleField($value)
    {
        return $this->createQueryBuilder('c')
           ->andWhere('c.exampleField = :val')
            ->setParameter('val', $value)
            ->orderBy('c.id', 'ASC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult()
        ;
    }
    */

    /*
    public function findOneBySomeField($value): ?Contact
    {
        return $this->createQueryBuilder('c')
            ->andWhere('c.exampleField = :val')
            ->setParameter('val', $value)
            ->getQuery()
            ->getOneOrNullResult()
        ;
    }
    */
}


Remarques :


Le « repository » va nous permettre d’interroger le contenu de la base de données. Il en existe un par entité.


Nous pouvons déjà réaliser plusieurs actions sur une entité, le fichier nous donne une notice.


Méthode qui retourne la valeur nulle ou un contact dont l’identifiant sera passé en paramètre.

@method Contact|null find($id, $lockMode = null, $lockVersion = null)


Méthode qui retourne la valeur nulle ou un contact par rapport à un ensemble de critères que nous pouvons passer en paramètre.

@method Contact|null findOneBy(array $criteria, array $orderBy = null)


Méthode qui retourne un tableau comprenant tous les contacts présents dans la table « Contact ».

@method Contact[] findAll()


Méthode qui retourne un tableau comprenant tous les contacts présents dans la table « Contacts » qui vérifient des critères passés en paramètre.

@method Contact[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)

Préparation de la migration

La préparation de la migration permet d’écrire automatiquement dans un fichier toutes les requêtes SQL nécessaires à la modification de la base de données.


Voici la commande :


php bin/console make:migration


Dans le répertoire « migrations », vous trouverez un nouveau fichier dont voici le contenu :

<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
 * Auto-generated Migration: Please modify to your needs!
 */
final class Version20211030113154 extends AbstractMigration
{
    public function getDescription(): string
    {
        return '';
    }

    public function up(Schema $schema): void
    {
        // this up() migration is auto-generated, please modify it to your needs
        $this->addSql('CREATE TABLE contact (id INT AUTO_INCREMENT NOT NULL, nom VARCHAR(30) NOT NULL, sujet VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL, message LONGTEXT NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
    }

    public function down(Schema $schema): void
    {
        // this down() migration is auto-generated, please modify it to your needs
        $this->addSql('DROP TABLE contact');
    }
}


Remarque : Nous voyons bien la requête SQL permettant de créer la table « Contact ».


Mise à jour de la base de données 


Pour l’instant, il n’y a rien de nouveau dans votre base de données « dbshare ». Il reste plus qu’une commande à exécuter :


php bin/console doctrine:migrations:migrate


Répondez « oui » à la question qui vous demande d’exécuter la mise à jour même s’il y a un risque de perdre des données.


Voici le résultat dans PhpMyAdmin : (cliquez sur « dbshare, puis sur « Contact » et enfin sur « Structure »).



Remarque :


Nous trouvons bien un identifiant « id » représenté par une clé jaune. Elle est en « AUTO_INCREMENT ».

Modification du formulaire


Dans « src/Controller/BaseController.php » :


Dans les « use » en haut du fichier, ajoutez l’importation de notre entité :


<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Form\ContactType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use App\Entity\Contact;

Puis dans le contrôleur « contact » :


#[Route('/contact', name: 'contact')]
    public function contact(Request $request, MailerInterface $mailer): Response
    {
        $contact = new Contact();
        $form = $this->createForm(ContactType::class, $contact);

        if($request->isMethod('POST')){
            $form->handleRequest($request);
            if ($form->isSubmitted()&&$form->isValid()){   
                $email = (new TemplatedEmail())
                ->from($contact->getEmail())
                ->to('reply@nuage-pedagogique.fr')
                ->subject($contact->getSujet())
                ->htmlTemplate('emails/email.html.twig')
                ->context([
                    'nom'=> $contact->getNom(),
                    'sujet'=> $contact->getSujet(),
                    'message'=> $contact->getMessage()
                ]);


Commentaires :


L’objectif du formulaire est de créer un nouveau contact. Ainsi nous déclarons une variable « $contact » et nous l’instancions « new Contact() ».

Nous donnons cette variable au formulaire et comme les noms des composants sont identiques aux noms des propriétés de l’entité, le formulaire va les associer automatiquement.

Par conséquent, pour récupérer l’email du formulaire, il suffit d’appeler l’accesseur « getEmail() » de la classe « Contact ». Il en est de même pour le sujet, le nom et le message.


Enregistrement des données dans la table « Contact »



#[Route('/contact', name: 'contact')]
    public function contact(Request $request, MailerInterface $mailer): Response
    {
        $contact = new Contact();
        $form = $this->createForm(ContactType::class, $contact);

        if($request->isMethod('POST')){
            $form->handleRequest($request);
            if ($form->isSubmitted()&&$form->isValid()){   
                $email = (new TemplatedEmail())
                ->from($contact->getEmail())
                ->to('reply@nuage-pedagogique.fr')
                ->subject($contact->getSujet())
                ->htmlTemplate('emails/email.html.twig')
                ->context([
                    'nom'=> $contact->getNom(),
                    'sujet'=> $contact->getSujet(),
                    'message'=> $contact->getMessage()
                ]);

                $em = $this->getDoctrine()->getManager();
                $em->persist($contact);
                $em->flush();
              
                $mailer->send($email);
                $this->addFlash('notice','Message envoyé');
                return $this->redirectToRoute('contact');
            }
        }

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



Commentaires :

$em = $this->getDoctrine()->getManager(); // Nous demandons au contrôleur dans lequel nous sommes ($this), de contacter «Doctrine » (getDoctrine()) afin de conserver dans la variable « $em » le manager (getManager()). Ce « manager » que nous appelons généralement l’« entity manager » va gérer, comme son nom l’indique, nos entités.

$em->persist($contact); // l’entité « manager » dit à Doctrine que nous allons probablement ajouter un contact.

Il confirme et réalise l’insertion d’un nouvel enregistrement dans la table « Contact ».



Testez en remplissant puis en soumettant le formulaire « Contact ».





Puis vérifiez sur PhpMyAdmin le contenu de la table « Contact ».






Voir le Chapitre 6