User Tools

Site Tools


backend:symfonyorm

Symfony : ORM

logo

ORM - Partie 1

Définition

Un ORM (object-relational mapping) est un type de programme informatique qui se place en interface entre un programme applicatif et une base de données relationnelle pour simuler une base de données orientée objet.
Ce programme définit des correspondances entre les schémas de la base de données et les classes du programme applicatif.

On utilisera de manière préférentielle Doctrine, mais on peut utiliser ce qu'on veut.

Site officiel de Doctrine

Base de données

Configuration

On peut définir des variables d'environnement dans les fichiers .env et .env.local.
Pour pouvoir utiliser Doctrine, il faut configurer la base de données dans un de ces fichiers.

Le fichier .env est dans les commits de Git
On peut créer un fichier .env.local pour définir des variables sensibles qu'on ne veut pas voir dans les commits (comme les accès aux bases de données par exemple). Les variables d'environnement définis dans ce fichier prennent le dessus sur les variables du fichier .env.

Création : doctrine:database:create

symfony console doctrine:database:create

Suppression : doctrine:database:drop --force

symfony console doctrine:database:drop --force

Exécution de code SQL : doctrine:query:sql

symfony console doctrine:query:sql le_code_sql

Entité

Création : make:entity

Du point de vue de Doctrine, une entity est un objet complété avec des informations de mapping qui lui permettent d'enregistrer correctement l'objet en base de données.

Une entity est une classe, donc son nom commence par une majuscule

Lorsqu'on crée une entity, cela correspond à une table dans notre base de données.

symfony console make:entity

On donne ensuite un nom à cette entité

Puis, on défini chaque champs de cette table en suivant les lignes de commandes.
Si on mets ?, on aura la liste des entrées possibles (pour les types par exemple)

Lorsque l'entity (Pin ici) est créée, 2 fichiers sont créés :
src/Entity/Pin.php
src/Repository/PinRepository.php

Modification : make:entity

Si on veut ajouter des propriétés à une entité (donc des champs à la table correspondante), on utilise :

symfony console make:entity

On donne ensuite le nom de l'entité qu'on souhaite modifier et on ajoute les prorpiétés souhaitées.

Migration

Création : make:migration

Une migration permet de générer le code SQL qui permet d'appliquer les changements apportés par l'entity.

symfony console make:migration

Lorsque la migration est créée, 1 fichier est créé :
src/Migrations/Version20200709154540.php

Les méthodes up et down sont créées, elles permettent d'appliquer ou d'annuler la migration.

On peut forcer le nom de la table pour l'entité, il faut ajouter @ORM\Table(name=“nomdetable”) en annotation :

/**
 * @ORM\Entity(repositoryClass=MonRepository::class)
 * @ORM\Table(name="nomdetable")
 */

On peut ajouter un message explicatif dans la méthode getDescription :

public function getDescription() : string
    {
        return 'Create pins table';
    }

Application : doctrine:migrations:migrate

Voir les migrations en attente
symfony console doctrine:migrations:status

Pour avoir plus de détails :

symfony console doctrine:migrations:status --show-versions
Appliquer une migration

Cela lance la méthode up

symfony console doctrine:migrations:migrate

ou

symfony console doctrine:migrations:migrate next
Annuler une migration

Cela lance la méthode down

symfony console doctrine:migrations:migrate prev

ORM - Partie 2

EntityManager

L'EntityManager est l'objet permettant d'effectuer les opérations liées à l'altération des données, à savoir les requêtes de type INSERT, UPDATE et DELETE.

Au niveau de l'application, même si on manipule des entités, qui correspondent aux données présentes en base, il ne suffit pas de modifier la valeur d'une propriété pour que Doctrine fasse la synchronisation en base de données. On devez gérer ces opérations avec l'EntityManager.

Pour l'instancier, on utilise :

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

Ajout de données

Méthode basique

Pour ajouter des données à la base de données, il faut les ajouter à l'entity et les faire persister avec les méthodes persist et flush:

public function index()
{
  $pin1 = new Pin;
  $pin2 = new Pin;
  $pin1->setTitle('Title 1');
  $pin2->setTitle('Title 1');
  $pin1->setDescription('Description 1');
  $pin2->setDescription('Description 1');
 
  $em = $this->getDoctrine()->getManager();
 
  $em->persist($pin1); // on indique qu'on veut faire persister $pin1
  $em->persist($pin2); // on indique qu'on veut faire persister $pin2
  $em->flush(); // on lance les persists
}

Injection de dépendances

Liste

On peut injecter des dépendances dans les constructeurs. Pour voir la liste des dépendances disponibles, il faut faire :

symfony console debug:autowiring

Les résultats sont de la forme :

EntityManager interface
Doctrine\ORM\EntityManagerInterface (doctrine.orm.default_entity_manager)
Utilisation

On injecte l'EntityManagerInterface dans la méthode __construct de l'entité et le stocke dans une propriété privée :

private $em;
 
public function __construct(EntityManagerInterface $em)
{
    $this->em = $em;
}
 
public function index()
{
  $pin1 = new Pin;
  $pin2 = new Pin;
  $pin1->setTitle('Title 1');
  $pin2->setTitle('Title 1');
  $pin1->setDescription('Description 1');
  $pin2->setDescription('Description 1');
 
  $this->em->persist($pin1);
  $this->em->persist($pin2);
  $this->em->flush();
}

On peut, uniquement dans les controllers, faire les injections de dépendances directement dans les méthodes, sans passer par la méthode __construct.

Cela donne :

public function index(EntityManagerInterface $em)
{
  $pin1 = new Pin;
  $pin1->setTitle('Title 1');
  $pin1->setDescription('Description 1');
 
  $em->persist($pin1);
  $em->flush();
}

Récupération de données

On doit utiliser le Repository correspondant à l'entité pour récupérer des données de la base de données.
Il dispose des méthodes suivantes :

  • find : récupère un enregistrement grace à son identifiant
  • findAll : récupère tous les enregistrements
  • findBy : récupère un ensemble d'enregistrements en se basant sur des critères
  • findOneBy : récupère un unique enregistrement en se basant sur des critères

On peut les voir dans l'annotation de l'entity :

/**
 * @method Pin|null find($id, $lockMode = null, $lockVersion = null)
 * @method Pin|null findOneBy(array $criteria, array $orderBy = null)
 * @method Pin[]    findAll()
 * @method Pin[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */

On utilise l'EntityManagerInterface pour accéder au repository grace à la méthode getRepository

$repo = $em->getRepository('App\Entity\Pin');

On peut utiliser App:Pin à la place de App\Entity\Pin :

$repo = $em->getRepository(App:Pin);

Ceci car App est défini dans le mapping du fichier config\packages\doctrine.yaml :

doctrine:
    orm:
        mappings:
            App:
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App

On peut aussi utiliser Pin::class :

$repo = $em->getRepository(Pin::class);

Pin::class a l'avantage de permettre un accès direct dans le code à l'entity Pin en faisant Ctrl+Clic gauche.

Ensuite on applique la méthode pour récupérer nos enregistrements :

public function index(EntityManagerInterface $em)
{
    $repo = $em->getRepository('Pin::class');
    $pins = $repo->findAll();
}

Affichage de données : Twig

Lorsqu'on souhaite afficher des données dans une template (une vue) on utilise Twig pour formater cet affichage.

On transmets les données avec la méthode render, en les passant en paramètres
On peut alors utiliser la variable $pins dans Twig sous la forme pins sans le $.

Paramètres sous forme de tableau associatif

public function index(EntityManagerInterface $em)
{
  $repo = $em->getRepository(Pin::class);
  $pins = $repo->findAll();
 
  return $this->render('pins/index.html.twig', [
    'pins' => $pins,
  ]);
}

Paramètres avec la fonction compact()

public function index(EntityManagerInterface $em)
{
  $repo = $em->getRepository(Pin::class);
  $pins = $repo->findAll();
 
  return $this->render('pins/index.html.twig', compact('pins'));
}

compact — Crée un tableau à partir de variables et de leur valeur

compact ( mixed $varname1 [, mixed $… ] ) : array

Pour chacun des arguments varname, …, compact() recherche une variable avec un même nom dans la table courante des symboles, et l'ajoute dans le tableau, de manière à avoir la relation nom ⇒ 'valeur de variable'.
C'est le contraire de la fonction extract().

Injection du Repository dans la fonction

public function index(PinRepository $repo)
{
  $pins = $repo->findAll();
 
  return $this->render('pins/index.html.twig', compact('pins'));
}

On peut même tout sur une seule instruction :

public function index(PinRepository $repo)
{
  return $this->render('pins/index.html.twig', [$pins = $repo->findAll()]);
}

ORM - Partie 3

Les formulaires

Approche naïve

Cette approche pose des problèmes de sécurité, voir les failles CSFR sur Wikipedia.

Le CSRF (cross-site request forgery) ou XSRF, est un type de vulnérabilité des services d'authentification web.

L'objet de cette attaque est de transmettre à un utilisateur authentifié une requête HTTP falsifiée qui pointe sur une action interne au site, afin qu'il l'exécute sans en avoir conscience et en utilisant ses propres droits.
L'utilisateur devient donc complice d'une attaque sans même s'en rendre compte. L'attaque étant actionnée par l'utilisateur, un grand nombre de systèmes d'authentification sont contournés.

Création

On crée les formulaire en HTML.

Récupération des données et insertion dans la base de données

On récupère les données grace à un objet ParameterBag et on les insère dans la base données grace à un objet EntityManagerInterface :

/**
  * @Route("/pins/create", methods="GET|POST")
  */
public function create(Request $request, EntityManagerInterface $em)
{
  if ($request->isMethod('POST')) {
    $data = $request->request->all();
 
    $pin = new Pin;
    $pin->setTitle($data['title']);
    $pin->setDescription($data['description']);
 
    $em->persist($pin);
    $em->flush();
 
    return $this->redirect('\');
  }
}

Après un formulaire de type POST, généralement on redirige l'utilisateur pour que le formulaire ne puisse pas être renvoyé.

On peut utiliser les noms de routes pour rediriger en utilisant la méthode generateUrl :

    return $this->redirect($this->generateUrl('app_home'));

On peut aussi utiliser redirectToRoute à la place :

    return $this->redirectToRoute('app_home');

On utilise redirect pour rediriger vers un lien extérieur au site, qui ne possède pas de route interne.

Approche Symfony : FormBuilder

Création

On va utiliser le FormBuilder pour construire des formulaires qui sont des objets avec lesquels on peut demander la vue, gérer la logique et faire d'autres choses…

Symfony gère automatiquement la partie Token CSRF

Pour créer un formulaire et donc un objet Form, on utilise les méthodes :

  • createFormBuilder() pour le créer
  • add pour y ajouter des champs.
  • getForm pour le récupérer
  • createView pour transmettre la vue associée à ce formulaire à Twig, au travers d'un objet FormView
/**
  * @Route("/pins/create", name="app_pins_create", methods="GET|POST")
  */
public function create(Request $request, EntityManagerInterface $em)
{
  $form = $this->createFormBuilder()
    ->add('title')
    ->add('description')
    ->getForm();
 
  return $this->render('pins/create.html.twig', [
    'monFormulaire' => $form->createView()
  ]);
}
Champs

Voir la documentation de Symfony : Form Types Reference.

Il existe plusieurs types de champs dans Symfony :

Type Champs
Text Text
Textarea
Email
Integer
Money
Number
Password
Percent
Search
Url
Range
Tel
Color
Choice Choice
Entity
Country
Language
Locale
Timezone
Currency
Date and Time Date
DateInterval
DateTime
Time
Birthday
Week
Other Checkbox
File
Radio
Groups Collection
Repeated
Hidden Hidden
Buttons Button
Reset
Submit
Base Form

Par défaut, Symfony va essayer de deviner le type d'un champs, mais on peut le définir :

    ->add('title', TextType::class)
    ->add('description', TextareaType::class)
    ->add('submit', SubmitType::class)
    ->getForm();

Ici, on ajoute un bouton 'submit' mais la manière utilisée n'est pas la bonne, il est préférable de l'ajouter directement dans TWIG : voir la section dans Twig.

Les champs possèdent des options qu'on peut passer en paramètres, telles que le label, value,… :

    ->add('submit', SubmitType::class, ['label' => 'Create Pin'])

On peut passer en paramètres des attribut HTML au champs avec 'attr' :

      ->add('title', TextType::class, ['attr' => ['autofocus' => true]])
      ->add('description', TextareaType::class, ['required' => false,'attr' => ['rows' => 10, 'cols' => 50]
      ])

On peut également passer des paramètres sous forme de tableau à la méthode createFormBuilder() pour préremplir les champs :

    $data = ['title' => 'Titre', 'description' => 'Description'];
    $form = $this->createFormBuilder($data)

Et on peut aussi lui passer un objet en paramètre.

Tous les champs créer avec la méthode add() (mise à part les boutons) doivent être présent dans l'objet, sinon il y aura une erreur.

    $pin = new Pin;
    $pin->setTitle('Titre');
    $pin->setDescription('Description');
    $form = $this->createFormBuilder($pin)

Dans ce cas Symphony connaît le type des champs grace à l'objet, on peut donc les enlevés :

      ->add('title', null, ['attr' => ['autofocus' => true]])
      ->add('description', null, ['attr' => ['rows' => 10, 'cols' => 50]])
Traitement

On utilise les méthodes :

  • handleRequest() : récupère les données uniquement en type POST
  • isSubmitted() : vérifie que le formulaire a été soumis (vérifie que le bouton de soumission a été cliqué)
  • isValid() : vérifie que le formulaire est valide
  • getData() : récupère les données du formulaire

On peut alors intégrer les données dans la base de données (setTitle(), setDescription(),persist() et flush()), puis on redirige l'urilisateur sur la page d'accueil :

/**
  * @Route("/pins/create", name="app_pins_create", methods="GET|POST")
  */
public function create(Request $request, EntityManagerInterface $em)
{
  $form = $this->createFormBuilder()
    ->add('title', TextType::class)
    ->add('description', TextareaType::class)
    ->add('submit', SubmitType::class, ['label' => 'Create Pin'])
    ->getForm();
 
  $form->handleRequest($request);
 
  if ($form->isSubmitted() && $form->isValid()) {
    $data = $form->getData();
 
    $pin = new Pin;
    $pin->setTitle($data['title']);
    $pin->setDescription($data['description']);
    $em->persist($pin);
    $em->flush();
 
    return $this->redirectToRoute('app_home');
  }
 
  return $this->render('pins/create.html.twig', [
    'monFormulaire' => $form->createView()
  ]);
}
/**
  * On donne les informations de la route
  */
public function create(Request $request, EntityManagerInterface $em)
{
    // On crée le formulaire
    // On ajoute un champs
    // On ajoute un champs
    // On ajoute un champs
    // On récupère le formulaire
 
  // Si la méthode est POST, on récupère la requête
 
  // Si le formulaire est soumis et valide 
    $data = // On récupère les données
 
    $pin = // On crée un objet Pin
    // On affecte le titre de $data au titre de Pin
    // On affecte la description de $data à la description de Pin
    // On indique qu'on veut faire persister les informations
    // On lance la persistance
 
    // On redirige vers la page d'accueil
  }
 
  // On envoie vers la page de création de pins, [
    // en passant la vue du formulaire en paramètre à Twig
  ]
}

On peut aussi utiliser les méthodes get()→getData pour ne plus passer par la variable $data :

    $pin->setTitle($form->get('title')->getData());
    $pin->setDescription($form->get('description')->getData());

On peut aussi se passer de la méthode get() et utiliser l'objet Form comme un tableau :

    $pin->setTitle($form['title']->getData());
    $pin->setDescription($form['description']->getData());

Si on à passer un objet en paramètre de la méthode createFormBuilder() , la méthode getData renverra l'objet modifié à la place d'un tableau, on peut alors mettre :

/**
  * @Route("/pins/create", name="app_pins_create", methods="GET|POST")
  */
public function create(Request $request, EntityManagerInterface $em)
{
  $pin = new Pin;
 
  $form = $this->createFormBuilder($pin)
    ->add('title', TextType::class, ['attr' => ['autofocus' => true]])
    ->add('description', TextareaType::class, ['attr' => ['rows' => 10, 'cols' => 50]])
    ->add('submit', SubmitType::class, ['label' => 'Create Pin'])
    ->getForm();
 
  $form->handleRequest($request);
 
  if ($form->isSubmitted() && $form->isValid()) {
    $em->persist($form->getData());
    $em->flush();
 
    return $this->redirectToRoute('app_home');
  }
 
  return $this->render('pins/create.html.twig', [
    'monFormulaire' => $form->createView()
  ]);
}

Et on peut aussi mettre $pin, à la place de $form→getData(), car sa valeur a été modifiée :

    $em->persist($pin);

ORM - Partie 4

Les Relations

Il existe plusieurs types de relations entre les champs des tables d'une base de donnée :

  • One-To-One
  • Many-To-One
  • One-To-Many
  • Many-To-Many

Dans Symfony, lorsqu'on crée une entité, on peut ajouter des propriétés de type relation.

Tutoriels

backend/symfonyorm.txt · Last modified: 2020/09/16 10:35 (external edit)