Symfony 6

Les fondamentaux

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Présentation

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Symfony est un « Framework » PHP dont l'objéctif est de proposer un cadre de travail complet et adapté à tout les besoins d'une application web.

Quelques liens utiles :

  • Symfony documentation : https://symfony.com/doc/current/index.html
  • Composer : https://getcomposer.org/
  • Le site officiel de PHP : https://www.php.net/docs.php

Présentation

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Symfony utilise PHP en "orienté objet". Il suit aussi les normes de développement PHP : PSR

Quelques liens utiles :

  • Le site officiel de PSR : https://www.php-fig.org/psr/
  • Le repo github de symfony : https://github.com/symfony

Présentation

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Symfony en plus d'être un framework, c'est aussi des composants indépendant qui peuvent être utilisé dans n'importe quelle projet PHP !

Installation

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Voici les prérequis afin de commencer à développer en symfony 5 !

Installation

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Vous pouvez vérifier l'installation en ouvrant un terminal et en entrant les commandes suivantes

# Affiche la version de php
php -v

# Affiche la version de composer
composer -v

# Affiche la version de l'utilitauire en ligne
# de commande de symfony
symfony -v

Installation

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Si php n'est pas présent en ligne de commande vous pouvez suivre ces tutoriel afin de le rendre accessible depuis votre ligne de commande :

Installation

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Afin de suivre cette formation il est recomandé d'utiliser vscode avec les extensions suivantes :

Installation

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Afin de commencer la formation, rendez vous dans votre dossier contenant vos projets et ouvrer un terminal

# Créé un nouveau projet symfony
symfony new formation-app --webapp 

Installation

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Ouvrez maintenant votre éditeur de code VSCode sur le répertoire « formation-app ». (Fichier > Ouvrir un dossier).

 

Vous pouvez ouvrir un terminal (View > Terminal).

Vous pouvez créer de nouveau terminaux (Terminal > New Terminal)

Permet de changer de terminal

Configuration

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Afin de pouvoir commencer la formation il nous faut configurer notre application afin de pouvoir nous connécter à notre base de donnée

Ouvrez le fichier ".env" et changer la valeur de configuration de "DATABASE_URL" :

# .env
DATABASE_URL="mysql://root@127.0.0.1:3306/pizza-lol?serverVersion=5.7"

Configuration

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Maintenant il nous faut créer la base de données de notre projet. Pour cela dans un terminal lancer la commande :

# Créer la base de données
symfony console doctrine:database:create

Configuration

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Maintenant nous somme prêt à lancer notre application ! Pour cela ouvrer un nouveau terminal et entrer la commande :

# Lance le server de dévelopement
symfony server:start --port=4444

Configuration

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Vous pouvez maintenant visiter notre application :

Architecture d'un projet Symfony

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient les exécutables nécessaire pour manipuler symfony. La console symfony s'y trouve par éxample.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient l'intégralité des fichiers de configuration. Nous passerons un peu de temps dans ce répertoire afin de configurer "bundle" et packages "flex".

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient des images de machines virtuel. Nous n'avons pas besoin de ce répertoire pour le moment :)

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient les « Migrations », soit les changement apporter à notre base de données durant notre dévelopement.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient les fichiers qui seront servis par notre serveur HTTP. Nous ne passerons que très peu de temps dans ce répertoire, mais c'est le point de départ d'un projet Symfony !

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient le code source de notre application. C'est ici que nous passerons le plus clair de notre temps. L'intégralité de nos fichiers PHP se trouverons ici.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient nos templates twig. C'est ici que nous trouvons le html que doit produire notre application par exemple.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient nos test automatisé ! C'est ici que nous pouvons tester notre application et s'assurer qu'elle fonctionne parfaitement bien.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient les fichiers de traductions. Utile si nous voulons rendre notre application multilingue.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient des fichiers généré par Symfony. C'est ici que nous pouvons retrouver le cache par exemple.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient l'intégralité des « Librairies » php que notre application utilise. C'est ici que l'on peut trouver le code source de Symfony par exemple.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Les fichier ".env", ".env.local", ".env.test" contiennent la configuration de notre application. C'est ici que nous définissons comment se connecter à une base de données par exemple.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient la liste des fichiers qui doivent être ignoré par notre system de versioning (Git).

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

"composer.json" et "composer.lock" contiennent les références des librairies que nous utilisons. Nous pouvons ainsi consulter les librairies à installer ainsi que leurs versions.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient le configuration des machines virtuel. Nous n'utiliserons pas ce fichier dans cette formation.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient la configuration nécessaire à notre librairie de test "php unit".

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient un petit texte explicatif de l'application. C'est le fichier qui s'affiche par défaut sur github !

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient les références des "packages flex" installé sur l'application. Nous aurons l'occasion d'en reparler.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient nos controllers.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient nos entities doctrine.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient nos formulaires.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

Architecture d'un projet Symfony

Contient nos repositories doctrine.

par David JEGAT - david.jegat@gmail.com - github.com/Djeg

MVC

Modèle Vue Controlleur

MVC: Model View Controller

Symfony utilise une « Architecture » qui permet d'organiser notre code grâce à 3 grands concept.

Cette architecture est très célèbre dans le millieu du web et porte le nom de MVC :

MVC: Model View Controller

Le Modèle (Model) représente les « données » de notre application.

 

Prenons notre pizzeria, nous pouvons imaginer un model "Pizza" qui contiendra le nom et le prix d'une pizza.

 

Symfony utilise la libraire « doctrine » afin de manipuler ces « données ».

 

Doctrine utilise lui aussi une architecture célèbre : Le Data Mapper Pattern.

MVC: Model View Controller

Un éxemple de « Model » : La pizza !

<?php

namespace App\Entity;

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

#[ORM\Entity(repositoryClass: PizzaRepository::class)]
class Pizza
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: "integer")]
    private $id;

    #[ORM\Column(type: "string", length: 255)]
    private $name;

    #[ORM\Column(type: "float")]
    private $price;

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

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getPrice(): ?float
    {
        return $this->price;
    }

    public function setPrice(float $price): self
    {
        $this->price = $price;

        return $this;
    }
}

MVC: Model View Controller

La Vue (View) contient la représentation de nos données sous un format qu'une machine peut afficher.

 

Le format le plus célèbre est HTML !

 

Symfony utilise Twig afin de « Formatter » nos données en HTML.

MVC: Model View Controller

Éxemple de Vue (view) avec twig:

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

{% block title %}Hello PizzaController!{% endblock %}

{% block body %}
<style>
    .example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
    .example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>

<div class="example-wrapper">
    <div class="row justify-content-between align-items-center mb-4">
        <div class="col">
            <h1>Liste des pizzas</h1>
        </div>
        <div class="col text-right">
            <a href="{{ path("pizza_create") }}" class="btn btn-primary">Créer une nouvelle pizza</a>
        </div>
    </div>

    <div class="row">
        {% if not data|length %}
            <h3 class="col text-center">Il n'y a pas encore de pizzas enregistré</h3>
        {% endif %}
        {% for unit in data %}
            <div class="col col-md-6">
                <div class="card">
                    <div class="card-body">
                        <p class="text-center">
                            La pizza <strong>{{ unit.pizza.name }}</strong> est à {{ unit.pizza.price }}€
                        </p>
                    </div>
                    <div class="card-footer">
                        <div class="row justify-content-center align-items-center mb-2">
                            <div class="col d-flex align-items-stretch">
                                <a
                                    href="{{ path('pizza_update', { id: unit.pizza.id }) }}"
                                    class="btn btn-primary col"
                                >
                                    Modifier cette pizza
                                </a>
                            </div>
                        </div>
                        {{ 
                            form(unit.deleteForm, {
                                action: path('pizza_delete', { id: unit.pizza.id })
                            })
                        }}
                    </div>
                </div>
            </div>
        {% endfor %}
    </div>
</div>
{% endblock %}

MVC: Model View Controller

Le contrôleur (Controller) contient le code qui vas « Gluer » nos modèles avec nos vue.

 

Il s'occupe de faire les différentes actions nécessaire afin d'afficher notre vue.

MVC: Model View Controller

Éxemple de Contrôleur (Controller) :

<?php

namespace App\Controller;

use App\Entity\Pizza;
use App\Form\PizzaType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class PizzaController extends AbstractController
{
    /**
     * List l'intégralité des pizzas disponible
     */
    #[Route("/pizzas", name: "pizza_list", methods: ["GET"])]
    public function list(): Response
    {
        $pizzas = $this
            ->getDoctrine()
            ->getRepository(Pizza::class)
            ->findAll();

        $data = [];

        foreach ($pizzas as $pizza) {
            $data[] = [
                'pizza' => $pizza,
                'deleteForm' => $this
                    ->createForm(PizzaType::class, $pizza, [
                        'delete' => true,
                    ])
                    ->createView(),
            ];
        }

        return $this->render('pizza/list.html.twig', [
            'data' => $data,
        ]);
    }

    /**
     * Créé une nouvelle pizza en utilisant le composant de formulaire
     */
    #[Route("/pizzas/new", name: "pizza_create", methods: ["GET", "POST"])]
    public function create(Request $request): Response
    {
        $pizza = new Pizza();

        $form = $this->createForm(PizzaType::class, $pizza);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $task = $form->getData();

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

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

            return $this->redirectToRoute('pizza_list');
        }

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

    /**
     * Met à jour une pizza
     */
    #[Route("pizza/{id}/update", name: "pizza_update", methods: ["GET", "POST"])]
    public function update(Pizza $pizza, Request $request): Response
    {
        $form = $this->createForm(PizzaType::class, $pizza);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $task = $form->getData();

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

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

            return $this->redirectToRoute('pizza_list');
        }

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

    /**
     * Suprime une pizza
     */
    #[Route("pizza/{id}/delete", name: "pizza_delete", methods: ["POST"])]
    public function delete(Pizza $pizza, Request $request): Response
    {
        $form = $this->createForm(PizzaType::class, $pizza, [
            'delete' => true,
        ]);
        $form->handleRequest($request);

        if (!$form->isSubmitted() || !$form->isValid()) {
            return $this->redirectToRoute('pizza_list');
        }

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

        $manager->remove($pizza);
        $manager->flush();

        return $this->redirectToRoute('pizza_list');
    }
}

Controller

Controller

Le controller est une classe qui contient au moins une méthode. Cette classe doit être obligatoirement suffixer par "Controller".

Chaque méthode de cette classe à pour objéctif de retourner une instance de « Response »

Les Controller doivent être localisé dans le répertoire: src/Controller

Controller

Éxemple de controller simple :

<?php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;

class HelloController
{
    public function hello(): Response
    {
        return new Response('Hello World !');
    }
}

Controller

Afin de fonctionner la méthode de notre controller doit être attaché à une « Route »

Une route est une annotation php, ou une entré dans un fichier de configuration.

Une route contient un chemin (path), un nom ainsi que les méthodes HTTP autorisé (GET, POST, PUT, PATCH ....)

Controller

Exemple de route :

<?php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class HelloController
{
    #[Route("/hello", name: 'hello', methods: ['GET'])]
    public function hello(): Response
    {
        return new Response('Hello World !');
    }
}

Chemin de notre route

Le nom de notre route (note: toujours préfixer par "app" afin d'éviter les conflits de nommage)

Les méthodes HTTP autorisées pour cette route. Ici "GET"

Controller

Convention de nommage des routes

Le nom de la route doit toujours être préfixé par "app_"

Il doit ensuite reprendre le nom du controller sans le suffix "Controller" (ex avec "HelloController":  "app_hello")

Finalement il doit reprendre le nom de la méthode : "app_hello_methode"

Controller

Convention de nommage des routes

Question : Pour un controller "PizzaController" et une méthode "list" comment dois-je nommé la route ?

Controller

Convention de nommage des routes

Question : Pour un controller "PizzaController" et une méthode "list" comment dois-je nommé la route ?

app_pizza_list

Controller

Les paramètres de la route

Une route peut posséder des variables comme par éxemple un identifiant.

Ces variables ce note {nomDeLaVariable} dans le chemin de la route

On peut récupérer la valeur de cette variable en la déclarant simplement dans les paramètres de notre méthode de controller

Controller

Les paramètres de la route

#[Route("/pizza/{id}/show", name: "app_pizza_show", methods: ["GET"])
public function show(int $id): Response
{
    // Nous pouvons maintenant utiliser l'id envoyé en paramètre ici !
    return new Response(sprintf("L'identifiant de la pizza est %s", $id));
}

Exemple de paramètre :

Controller

Les paramètres de la route

Exemple de paramètre :

Le nom du paramètre: ici "id"

Le paramètre peut ensuite être récupéré simplement en l'ajoutant dans notre méthode. Attention les noms doivent être les même !

#[Route("/pizza/{id}/show", name: "app_pizza_show", methods: ["GET"])
public function show(int $id): Response
{
    // Nous pouvons maintenant utiliser l'id envoyé en paramètre ici !
    return new Response(sprintf("L'identifiant de la pizza est %s", $id));
}

Controller

Il est possible dans n'importe quelle méthode de controller d'accéder à la « Request » en la rajoutant en paramètre de la méthode.

La request contient les informations envoyer par le client HTTP (headers, queries, data ...)

Controller

Exemple d'utilisation de la « Request » :

<?php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class HelloController
{
    #[Route("/hello", name: "app_hello", methods: ["GET"])
    public function hello(Request $request): Response
    {
        $name = $request->query->get('name', 'World');

        return new Response(sprintf('Hello %s !', $name));
    }
}

Controller

Vous pouvez en apprendre plus sur la « Request » et la « Response » ici !

Controller

Il éxiste une class « AbstractController »

Cette classe contient des méthodes facilitant l'utilisation du controller.

Elle peut être hérité depuis nos controller afin de faciliter notre travail.

Controller

Hériter de AbstractController

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class HelloController extends AbstractController
{
    #[Route("/hello", name: "app_hello", methods: ["GET"])
    public function hello(Request $request): Response
    {
        $name = $request->query->get('name', 'World');

        return new Response(sprintf('Hello %s !', $name));
    }
}

Controller

Les controller peuvent être généré en utilisant une commande Symfony depuis un terminal !

# Créer un nouveau controller « HelloController »
symfony console make:controller hello

Controller

Créer un Controller « HelloController »

Récupérer le nom en utilisant la Request:

$name = $request->query->get('name', 'le nom par défaut');

Créer une méthode nommé "hello"

Retourner une nouvelle réponse avec le nom récupéré

Lui attacher une Route "app_hello_hello" avec le chemin "/hello"

Exercice !

Controller

Quelques méthodes utile de AbstractController

// Créer une response de redirection vers une autre route
return $this->redirectToRoute('app_home');

// Créer l'url compléte d'une route
$url = $this->generateUrl('app_home');

// Créer un formulaire
$form = $this->createForm(SomeType::class);

// Retourne une réponse avec le contenue d'un template twig
return $this->render('some/template.html.twig', [ 'name' => 'john doe' ]);

Le Model

Doctrine

Le Model: Doctrine

Doctrine est la librairie utilisé afin de créer des modèles.

Les modèles sont représenté par des objets PHP que l'on nomme « Entity »

Chaque « Entity » est rattaché à une table de la base de données

Chaque propriétés de notre entité correspond à une colone de la table.

Ces colones sont configuré via des annotations PHP

Chaque entités se place dans le répertoire src/Entity

Le Model: Doctrine

Afin de « récupérer » des données depuis notre base de données, Doctrine utilise des objets nommés "Repository".

Ces « Repository » contientnt autant de méthodes que l'on souhaite.

Chaque méthodes récupère une ou plusieurs entités.

Les « Repository » permettent d’exécuter des requêtes à notre base de données.

Le Model: Doctrine

Petit schéma récapitulatif :

Entity

  • contient les données
  • C'est une simple class PHP
  • représente une table de notre base de données

Repository

  • permet de récupérer des entités depuis notre base de données.
  • Enregistre et supprime les entités dans la base de données

Le Model: Doctrine

Générer une entité et son Repository

On peut utiliser la console symfony afin de générer nos entités et repository :

# Génére une entité Pizza dans src/Entity ainsi
# que son repository PizzaRepository dans src/Repository
symfony console make:entity Pizza

Le Model: Doctrine

Exemple d'Entity : La Pizza

<?php

namespace App\Entity;

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

#[ORM\Entity(repositoryClass: PizzaRepository::class)]
class Pizza
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private $id;

    #[ORM\Column(type: 'string', length: 255)]
    private $name;

    #[ORM\Column(type: 'float')]
    private $price;

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

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getPrice(): ?float
    {
        return $this->price;
    }

    public function setPrice(float $price): self
    {
        $this->price = $price;

        return $this;
    }
}

Le Model: Doctrine

Exemple d'Entity : La Pizza

<?php

namespace App\Entity;

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

#[ORM\Entity(repositoryClass: PizzaRepository::class)]
class Pizza
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private $id;

    #[ORM\Column(type: 'string', length: 255)]
    private $name;

    #[ORM\Column(type: 'float')]
    private $price;

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

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getPrice(): ?float
    {
        return $this->price;
    }

    public function setPrice(float $price): self
    {
        $this->price = $price;

        return $this;
    }
}

Définie l'entité ainsi que le nom de la table en base de données. Attache le Repository

Définie une colone de type ID

Auto génére l'ID

Définie une colone de type string ou integer

Le Model: Doctrine

Exemple d'Entity : La Pizza

<?php

namespace App\Entity;

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

#[ORM\Entity(repositoryClass: PizzaRepository::class)]
class Pizza
{
    // ...
    
    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

}

Les entités contienent des getters et setters afin de récupérer et changer les données

Le Model: Doctrine

Enregistré une entité depuis un Controller qui hérite de AbstractController

<?php

namespace App\Controller;

// use ...

class PizzaController extends AbstractController
{
    #[Route('/pizza/nouvelle', name: 'app_pizza_new', methods: ['GET'])]
    public function newPizza(PizzaRepository $repository): Response
    {
        // création d'une nouvelle pizza
        $pizza = new Pizza();
        $pizza->setName('Régina');
        $pizza->setPrice(9.9);

        // Enregistrement de la pizza dans la base de données
        // en utilisant le PizzaRepository
        $repository->add($pizza, true);

        return new Response(
            "La pizza avec l'id {$pizza->getId()} à bien été enregistré"
        );
    }
}

Le Model: Doctrine

Enregistré une entité depuis un Controller qui hérite de AbstractController

<?php

namespace App\Controller;

// use ...

class PizzaController extends AbstractController
{
    #[Route('/pizza/nouvelle', name: 'app_pizza_new', methods: ['GET'])]
    public function newPizza(PizzaRepository $repository): Response
    {
        // création d'une nouvelle pizza
        $pizza = new Pizza();
        $pizza->setName('Régina');
        $pizza->setPrice(9.9);

        // Enregistrement de la pizza dans la base de données
        // en utilisant le PizzaRepository
        $repository->add($pizza);

        return new Response(
            "La pizza avec l'id {$pizza->getId()} à bien été enregistré"
        );
    }
}

On hérite de la class AbstractController

Le Model: Doctrine

Enregistré une entité depuis un Controller qui hérite de AbstractController

<?php

namespace App\Controller;

// use ...

class PizzaController extends AbstractController
{
    #[Route('/pizza/nouvelle', name: 'app_pizza_new', methods: ['GET'])]
    public function newPizza(PizzaRepository $repository): Response
    {
        // création d'une nouvelle pizza
        $pizza = new Pizza();
        $pizza->setName('Régina');
        $pizza->setPrice(9.9);

        // Enregistrement de la pizza dans la base de données
        // en utilisant le PizzaRepository
        $repository->add($pizza);

        return new Response(
            "La pizza avec l'id {$pizza->getId()} à bien été enregistré"
        );
    }
}

On injécte le PizzaRepository dans la paramètres de la fonction

Le Model: Doctrine

Enregistré une entité depuis un Controller qui hérite de AbstractController

<?php

namespace App\Controller;

// use ...

class PizzaController extends AbstractController
{
    #[Route('/pizza/nouvelle', name: 'app_pizza_new', methods: ['GET'])]
    public function newPizza(PizzaRepository $repository): Response
    {
        // création d'une nouvelle pizza
        $pizza = new Pizza();
        $pizza->setName('Régina');
        $pizza->setPrice(9.9);

        // Enregistrement de la pizza dans la base de données
        // en utilisant le PizzaRepository
        $repository->add($pizza, true);

        return new Response(
            "La pizza avec l'id {$pizza->getId()} à bien été enregistré"
        );
    }
}

On enregistre la pizza dans la base de données

Le Model: Doctrine

Récupérer des entités depuis notre controller en utilisant un Repository

<?php

namespace App\Controller;

// use ...

class PizzaController extends AbstractController
{
    #[Route('/pizza/nouvelle', name: 'app_pizza_new', methods: ['GET'])]
    public function newPizza(PizzaRepository $repository): Response
    {
        // Récupération d'une pizza par son id
        $pizza = $repository->find(1);

        // Récupération de la toutes les pizzas
        $pizzas = $repository->findAll();
        
        // Supprime une pizza !
        $repository->remove($pizza, true);
        
        // ...
    }
}

Le Model: Doctrine

Mettre à jour notre base de données

Symfony fournie une suite de ligne de commande afin de gérer notre base de données :

# Créer la base de données
symfony console doctrine:database:create

# Supprime la base de données
symfony console doctrine:database:drop --force

# Met à jour le schéma de la base de données
symfony console doctrine:schema:update --force

Le Model: Doctrine

Exercice !

  1. Créer une entité et un repository Pizza
  2. ajouter les champs "name" (string) et "price" (float).
  3. Créer un controller "PizzaController"
  4. Créer une méthode "generate" avec une route "/pizza/generate" (suivre les conventions de nommage)
  5. Créer et enregistré une pizza en récupérant les données depuis la requête
  6. Retourner une réponse avec le contenue 'Ok {idDeLaPizza}'

Le Model: Doctrine

Petit Bonus ! Les ParamConverter

Lorsque l'on créé un paramètre de route le composant FrameworkExtraBundle permet de directement résoudre nos entités sana passer par un repository !

Il suffit pour cela de simplement "typer" notre paramètre

Ainsi que de s'assurer que le nom de notre paramètre de route est bien une propriété de notre entité

Le Model: Doctrine

Petit Bonus ! Les ParamConverter : Éxemple

#[Route('/pizza/{id}/show', name: 'app_pizza_show', methods: ['GET'])]
public function show(Pizza $pizza): Response
{
    // Nous pouvons maintenant utiliser la pizza résolu
    // depuis l'id dans notre route
    return new Response(sprintf("La pizza %s", $pizza->getName()));
}

Le Model: Doctrine

Petit Bonus ! Les ParamConverter : Éxemple

"id" est une propriété de l'entité Pizza

Symfony résous tout seul notre pizza en appelant "repository->find(..)" !

Si aucune pizza n'est présente dans la base de données, Symfony lève une erreur HTTP 404

#[Route('/pizza/{id}/show', name: 'app_pizza_show', methods: ['GET'])]
public function show(Pizza $pizza): Response
{
    // Nous pouvons maintenant utiliser la pizza résolu
    // depuis l'id dans notre route
    return new Response(sprintf("La pizza %s", $pizza->getName()));
}

La Vue

Twig

La Vue : Twig

La vue permet d'afficher nos données dans un format que la machine comprend.

Le format le plus célèbre dans le web est HTML. Mais il en existe d'autre ! (xml, yaml, json ...)

Afin d'afficher du html Symfony utilise un moteur de template : Twig

Il nous permet d'écrire et de mettre en forme nos données en HTML facilement, avec une syntaxe allégé.

La Vue : Twig

Les fichiers twig sont localisé dans le répertoire templates

Il porte le nom suivant: "monFichier.monFormat.twig"

Éxemple : "liste_produits.html.twig"

Ils doivent suivre une convention de nommage lorsqu'il sont attaché à un controller (nous verrons cela).

La Vue : Twig

Éxemple d'un fichier HTML en Twig

<html>
    <head>
        <title>Welcome to Symfony!</title>
    </head>
    <body>
        <h1>{{ page_title }}</h1>

        {% if user.isLoggedIn %}
            Hello {{ user.name }}!
        {% endif %}

        {# ... #}
    </body>
</html>

La Vue : Twig

Quelques référence rapide :

{# Ceci est un commentaire twig #}

{# affiche le contenue d'une variable #}
<p>Bonjour {{ name }}</p>

{# Affiche un contenue selon certaines condition #}
{% if age > 18 %}
    <p>Vous êtes majeur !</p>
{% else if age <= 4 %}
    <p>Areee Aree Areeeee</p>
{% else %}
    <p>Vous êtes mineur !</p>
{% endif %}

{# boucle sur les éléments d'un tableau #}
{% for name in items %}
    <p>Vous possédez {{ name }} !</p>
{% endfor %}

{#
    Accède à la propriété d'un object:

    ¨ Twig utilise les getters et setters afin d'obtenir
    la propriété d'un object. La commande plus haut éxécute:
    $user->getName(); ¨
#}
Bonjour {{ user.name }} !

La Vue : Twig

Afficher un template depuis un controller que Hérite de AbstractController

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class TestController extends AbstractController
{
    #[Route('/test/display', name: 'app_test_display')]
    public function display(): Response
    {
    	// Retourne une instance de Response avec le contenue html
        // de notre template
    	return $this->render('some_folder/some_template.html.twig', [
            // Définie les variables accessible dans notre template
            // twig
            'username' => 'john',
            'email' => 'john@mail.com',
            'is_major' => false,
        ]);
    }
}

La Vue : Twig

Convention de nommage des templates !

Correspond au nom de notre controller !

Correspond au nom de notre méthode !

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class TestController extends AbstractController
{
    #[Route('/test/display', name: 'app_test_display')]
    public function display(): Response
    {
    	// Retourne une instance de Response avec le contenue html
        // de notre template
    	return $this->render('some_folder/some_template.html.twig', [
            // Définie les variables accessible dans notre template
            // twig
            'username' => 'john',
            'email' => 'john@mail.com',
            'is_major' => false,
        ]);
    }
}

La Vue : Twig

Exercice !

  1. Créer une méthode "list" dans le pizza controller
  2. Attacher une route "/pizza/list" avec un nom correct
  3. Récupérer toutes les pizzas en utilisant un repository doctrine
  4. Créer un template dans `templates/pizza/list.html.twig`
  5. Ajouter du HTML basic avec une instructions "for" sur les pizzas
  6. Rendre le template dans notre controller en lui donnant les pizzas

La Vue : Twig

Inclusion et Héritage de block

Twig est un très puissant moteur de template qui permet l'inclusion et l'héritage d'autre template.

La Vue : Twig

Inclusion et Héritage de block

Prenons un simple template : '_menu.html.twig'

<nav>
    <ul>
        <li>
            <a href="#">Menu 1</a>
        </li>
        <li>
            <a href="#">Menu 2</a>
        </li>
        <li>
            <a href="#">Menu 3</a>
        </li>
    </ul>
</nav>

La Vue : Twig

Inclusion et Héritage de block

nous pouvons inclure ce template ou nous le souhaitons en utilisant la fonction twig "include":

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Welcome !</title>
    </head>
    <body>
        {#
            affiche le menu à cette endroit !

            Nous pouvons aussi envoyé des variables à notre template
            inclue.
        #}
        {{ include('_menu.html.twig', { name: pizza.name }) }}
    </body>
</html>

La Vue : Twig

Inclusion et Héritage de block

Les template que nous incluons avec la commande "include" doivent toujours être préfixé par un "_"

Éxemple :

_menu.html.twig

_header.html.twig

blog/_article_card.html.twig

pizza/_pizza_detail.html.twig

La Vue : Twig

Inclusion et Héritage de block

L'héritage est un system encore plus puissant basé sur l'utilisation de block.

La Vue : Twig

Inclusion et Héritage de block

Reprenons une page html simple : "base.html.twig"

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Welcome !</title>
    </head>
    <body>
    </body>
</html>

La Vue : Twig

Inclusion et Héritage de block

Définissons des block qui pourront être redéfinie plus tard :

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block page_title %}Welcome !{% endblock %}</title>
    </head>
    <body>
    	{% block body %}{% endblock %}
    </body>
</html>

La Vue : Twig

Inclusion et Héritage de block

Maintenant créons un template qui hérite de notre base : "list.html.twig"

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

La Vue : Twig

Inclusion et Héritage de block

Grace à ce extends nous pouvons réécrire des blocks :

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

{# nous redéfinissons le titre de le page #}
{% block page_title %}
	Liste des pizzas - yum yum
{% endblock %}

La Vue : Twig

Inclusion et Héritage de block

Attention, le HTML en dehors des blocks ne s'affichera jamais !

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

{#
    ce template "extends" d'un template parent, le html
    qui suit ne s'affichera jamais car il n'est pas dans un "block"
#}
<div>ne s'affichera jamais</div>

{# nous redéfinissons le titre de le page #}
{% block page_title %}
	Liste des pizzas - yum yum
{% endblock %}

La Vue : Twig

Inclusion et Héritage de block

Il faut obligatoirement intégrer notre html dans un block

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

{# affiche dans la balise body #}
{% block body %}
    <div>S'affichera dans la balise body</div>
{% endblock %}

{# nous redéfinissons le titre de le page #}
{% block page_title %}
	Liste des pizzas - yum yum
{% endblock %}

La Vue : Twig

Exercice !

  1. Créer un fichier `templates/base.html.twig` avec 2 block "title" et "body"
  2. Retoucher le fichier "pizza/list.html.twig" pour qu'il hérite "base.html.twig" et redéfinisse les block "title" et "body".

La Vue : Twig

Quelques fonctions twig très utile !

{# inclue un autre template #}
{{ include('_some_template.html.twig', { name: 'john doe' }) }}

{# affiche le cheming complet d'une route #}
{{ path('app_home') }} {# /home par éxemple #}

{# affiche l'url compléte d'une route #}
{{ url('app_home') }} {# https://mon-site.com/home par exemple #}

Les formulaires

Comment traiter des données

Les formulaires

Les formulaires permettent de gérer les données rentré par un utilisateur

Ils occupent une place centrale dans n'importe quelle application web !

Symfony fourni un composant Form

Ce composant permet de définir des champs, de les valider et finalement des les transformer en objet PHP

Les formulaires

Les formulaires sont représenté par des class de Type

Ces classes hérite de AbstractType

Elles possèdent une méthode "buildForm" qui définie les champs de notre formulaire

Elles possèdent une méthode "configureOptions" qui définie les options de notre formulaire comme par exemple l'objet PHP que nous voulons récupérer lorsque notre formulaire est valide

Les formulaires se situe dans le répertoire `src/Form`

Les formulaires

Un éxemple de formulaire : PizzaType

<?php

namespace App\Form;

...

class PizzaType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class)
            ->add('price', MoneyType::class)
            ->add('submit', SubmitType::class);
    }

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

Les formulaires

Un éxemple de formulaire : PizzaType

<?php

namespace App\Form;

...

class PizzaType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class)
            ->add('price', MoneyType::class)
            ->add('submit', SubmitType::class);
    }

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

Ajout d'un champ "name". Correspond à la propriété "name" de notre entité Pizza

Définie le type de champ, ici "Text"

Définie la class PHP que notre formulaire doit remplir

Les formulaires

Nous pouvons générer une class de formulaire très simplement en utilisant la ligne de commande de symfony :

# Créer un formulaire "PizzaType"
symfony console make:form pizza

Les formulaires

Utiliser un formulaire depuis un Controller

// Créer un formulaire avec aucune données
$form = $this->createForm(PizzaType::class);

// Créer un formulaire et préremplie les champs
// avec la pizza
$pizza = new Pizza();
$pizza->setName('Régina');
$pizza->setPrice(9.9);

$form = $this->createForm(PizzaType::class, $pizza);

// Vérifie si le formulaire a était envoyé
$form->isSubmitted();

// Vérifié si le formulaire est valide
$form->isValid();

// Remplie le formulaire avec les données de la requête
$form->handleRequest($request);

// Récupére la l'objet PHP qu'a rémplie notre formulaire
$pizzaValidated = $form->getData();

// Créer la vue de notre formulaire qui peut être
// envoyé à twig pour l'affichage
$view = $form->createView();

Les formulaires

Exemple concret d'utilisation de formulaire dans un controller

// Création de notre objet PHP qui sera "remplie"
// par notre formulaire
$pizza = new Pizza();
// Création de notre formulaire
$form = $this->createForm(PizzaType::class, $pizza);
// Maintenant nous replissons le formulaire et notre
// objet php avec la requête
$form->handleRequest($request);
// Nous vérifions si le formulaire à bien était envoyé
// et que les données sont valide
if ($form->isSubmitted() && $form->isValid()) {
    // On récupére l'objet PHP validé et remplie par notre
    // formulaire
    $validPizza = $form->getData();
    // Nous enregistrons l'objet en base de données
    $manager->persist($validPizza);
    $manager->flush();
    // Finalement on redirige l'utilisateur vers une
    // page d'acceuil
    return $this->redirectToRoute('app_home');
}
// Si le formulaire n'est pas valide alors nous
// l'affichons dans un template en récupérant la "View"
// de notre formulaire
$formView = $form->createView();
// Maintenant nous affichons le template avec le formulaire
return $this->render('some/template.html.twig', [
    'form' => $formView,
]);

Les formulaires

Afficher un formulaire dans un template twig

    {# affiche l'intégralité du formulaire #}
    {{ form(form) }}

    {# affiche le début de la balise form #}
    {{ form_start(form) }}

    {# affiche la label d'un champ #}
    {{ form_label(form.name) }}

    {# affiche les erreurs d'un champ #}
    {{ form_errors(form.name) }}

    {# affiche le widget d'un champ #}
    {{ form_widget(form.name) }}

    {# Affiche le label, les erreurs et le widget d'un champ #}
    {{ form_row(form.name) }}

    {# Affiche le reste d'un formulaire #}
    {{ form_rest(form) }}

    {# Affiche la fin de la balise form #}
    {{ form_end(form) }}

Les formulaires

Exercice !

  1. Créer un formulaire "PizzaType" avec les champs "name", "price" et "submit" de type "TextType", "MoneyType" et "SubmitType"
  2. Créer une méthode dans PizzaController nommé "create"
  3. Créer une route pour cette méthode "/pizza/create"
  4. Utiliser le formulaire afin de soumettre et valider notre formulaire dans la méthode create de PizzaController
  5. Enregistrer l'entité dans doctrine et redirigé vers "app_pizza_list"
  6. Afficher le formulaire dans un template "pizza/create.html.twig" qui hérite de "base.html.twig"

Les formulaires

La protéction CSRF

Chaque formulaire créé un champ "hidden" avec un token CSRF

Ce champ est ensuite validé lorsque nous faisons "$form->isValid()"

Ce champ empêche les attaques CSRF, la falsification de nos formulaire et s'assure que chaque formulaire envoyé sois bien celui que nous avons créé au préalable

Les formulaires

La protéction CSRF

Par défaut tout les "Type" on une protection CSRF.

Les formulaires

Les options de formulaire

Chaque "Type" de formulaire, possède des options

Ces options permettent de configurer le type en question (son label, sa valeur etc ...)

Nous pouvons définir nos propres options grace à la méthode "configureOptions" de notre "Type"

Les formulaires

Les options de formulaire, quelques exemples avec le TextType

$builder
    ->add('name', TextType::class, [
        // Définie le label de ce champ
        'label' => 'Nom de la pizza',
        // Indique si ce champ est requis ou pas
        'required' => true,
        // Indique si ce champ doit être "mappé" à notre objet PHP
        'mapped' => true,
    ]);

Les formulaires

Vous pouvez retrouver la liste compléte des Type et options disponible dans la documentation de Symfony !

Les formulaires

Les options personalisé

Les options personalisé se rajoute dans la méthode "configureOptions"

Elles permettent de créer des "Type" plus riche 

Nous pouvons utiliser ces options dans la méthode "buildForm" afin de changer nos champs de formulaire

Les formulaires

Exemple d'une option personalisé pour créer un formulaire de suppression

class PizzaType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if (true === $options['delete_mode']) {
            $builder->add('submit', SubmitType::class, [
                'label' => 'Supprimer cette pizza',
            ]);

            return;
        }

        $builder
            ->add('name', TextType::class)
            ->add('price', MoneyType::class)
            ->add('submit', SubmitType::class);
    }

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

Les formulaires

Exemple d'une option personalisé pour créer un formulaire de suppression

class PizzaType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if (true === $options['delete_mode']) {
            $builder->add('submit', SubmitType::class, [
                'label' => 'Supprimer cette pizza',
            ]);

            return;
        }

        $builder
            ->add('name', TextType::class)
            ->add('price', MoneyType::class)
            ->add('submit', SubmitType::class);
    }

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

Ajout d'une option "delete_mode" qui vaut "false" par défaut

Si delete_mode est vrai, nous créons simplement un bouton d'envoie

Les formulaires

Envoyer des options à nos formulaires créé dans un controller

// Créé un formulaire PizzaType avec l'option
// "delete_mode" à vrai
$form = $this->createForm(PizzaType::class, $pizza, [
    'delete_mode' => true,
]);

Exercice Final

Pizza CRUD !

Exercice Final : Pizza Crud !

Prérequis

Basculer sur la branche "sf5-foundation/base" en exécutant les commandes suivantes :

# On commit notre espace de travail
git add .
git commit -m "exo symfony"

# Récupére les branches depuis github
git fetch --all

# Se rendre sur la branche sf5-foundation/base
git checkout sf5-foundation/base

Exercice Final : Pizza Crud !

Objéctif

Pouvoir créer, lister, mettre à jour et supprimer des pizzas !

Exercice Final : Pizza Crud !

Créer une entité "Pizza" avec un champ "name" et "price" de type "string" et "float"

Créer un controller "AdminPizzaController"

Créer un FormType "AdminPizzaType"

Exercice Final : Pizza Crud !

Créer une méthode "list" dans AdminPizzaController avec une route "/admin/pizza/list"

Récupérer les pizzas depuis la base de données en utilisant le repository PizzaRepository

Afficher la list des pizzas dans un template "admin_pizza/list.html.twig"

Exercice Final : Pizza Crud !

Créer une méthode "create" dans AdminPizzaController

Attacher la route "/admin/pizza/create" à cette méthode

Créer et gérer les données de la requête avec le formulaire "AdminPizzaType"

Si le formulaire est envoyé et valide, enregistré la pizza en base de données et rediriger vers la liste des pizzas

Sinon, afficher la vue du formulaire dans un template "amin_pizza/create.html.twig"

Exercice Final : Pizza Crud !

Créer une méthode "update" dans AdminPizzaController

Attacher la route "/admin/pizza/{id}/update" à cette méthode

Créer et gérer les données de la requête avec le formulaire "AdminPizzaType"

Si le formulaire est envoyé et valide, enregistré la pizza en base de données et rediriger vers la liste des pizzas

Sinon, afficher la vue du formulaire dans un template "amin_pizza/update.html.twig"

Récupérer la pizza avec l'id envoyé depuis la route en utilisant le repository PizzaRepository

Exercice Final : Pizza Crud !

Créer une méthode "delete" dans AdminPizzaController

Attacher la route "/admin/pizza/{id}/delete" à cette méthode

Supprimer la pizza en utilisant le manager doctrine

Rediriger vers la liste des pizzas

Récupérer la pizza avec l'id envoyé depuis la route en utilisant le repository PizzaRepository

Les Relations

Les Relations

Doctrine met à disposition un puissant système de relation entre nos entitès

 

Les Possible relations sont au nombre de 3

- OneToOne : Lier une entité à une autre entité

- ManyToOne ou OneToMany : Lier une entité à plusieurs entités

 

- ManyToMany : Lier plusieurs entité à plusieurs entités

Les Relations

Pour mettre en place une relation il suffit de lancer la commande suivante :

symfony console make:entity Pizza

Les Relations

Symfony vous demandera le nom de la propriété dans pizza qui contiendra notre relation :

Ici ingredients étant données que notre pizza possède plusieurs ingrédients

Les Relations

Maintenant choisisez le type de champs relation et laisser vous guider :)

Les Relations

Un fois cette commande terminé, symfony rendra possible l'accès à cette relation dans l'entité pizza (getIngredients etc ...) :

<?php

namespace App\Entity;

// use ....

#[ORM\Entity(repositoryClass: PizzaRepository::class)]
class Pizza
{
    // ...

    #[ORM\ManyToMany(targetEntity: Ingredient::class)]
    private $ingredients;

    /**
     * @return Collection<int, Ingredient>
     */
    public function getIngredients(): Collection
    {
        return $this->ingredients;
    }

    public function addIngredient(Ingredient $ingredient): self
    {
        if (!$this->ingredients->contains($ingredient)) {
            $this->ingredients[] = $ingredient;
        }

        return $this;
    }

    public function removeIngredient(Ingredient $ingredient): self
    {
        $this->ingredients->removeElement($ingredient);

        return $this;
    }
}

Les Fixtures

Les Fixtures

Les fixtures permettent de créer une base données complète en très peu de temps avec des données de test !

 

Avoir des fixtures c'est s'assurer du bon fonctionnement de l'application, mais aussi pouvoir travailler en équipe :)

 

Pour "charger" ces fixtures, il éxiste un "bundle" (extension de symfony) :

hautelook/alice-bundle

Les Fixtures

Installation du bundle

symfony composer require hautelook/alice-bundle

Les Fixtures

Créons notre premier fichier de fixtures afin de remplir notre base de données

 

Pour cela il nous faut créer le fichier "fixtures/data.yml" avec le contenu suivant :

# fixtures/data.yml
App\Entity\Ingredient:
  ingredient1:
    name: 'Mon premier ingredient'

Les Fixtures

# fixtures/data.yml
App\Entity\Ingredient:
  ingredient1:
    name: 'Mon premier ingredient'

Contient le nom de l"entité que nous voulons générer

Les Fixtures

# fixtures/data.yml
App\Entity\Ingredient:
  ingredient1:
    name: 'Mon premier ingredient'

Contient un identifiant libre. Vous pouvez renseigner ce que vous souhaitez ici, cela nous sera utile pour les relations !

Les Fixtures

# fixtures/data.yml
App\Entity\Ingredient:
  ingredient1:
    name: 'Mon premier ingredient'

Ici nous renseignons les "propriétés" de la class Ingredient que nous devons remplir

Les Fixtures

symfony console hautelook:fixtures:load

Afin de charger ce fichier dans notre base de données, une seule commande !

Les Fixtures

Il est possible avec Alice de générer "range" de données :

App\Entity\Ingredient:
  ingredient{1..100}:
    name: 'Mon Super Ingrédient'

Les Fixtures

App\Entity\Ingredient:
  ingredient{1..100}:
    name: 'Mon Super Ingrédient'

Génére 1 à 100 ingrédient !!

Les Fixtures

App\Entity\Ingredient:
  ingredient{1..100}:
    name: "<word()>"

Il est aussi possible de générer de fausse données :

Vous retrouverez la liste des toutes les fausses données que vous pouvez générer ici

 

Alice utilise fakerPHP pour générer ses fausses données

Génére un mot aléatoire

Les Fixtures

App\Entity\Ingredient:
  ingredient{1..100}:
    name: "<word()>"

App\Entity\Pizza:
  pizza1:
    name: "Régina"
    price: 8.8
    ingredients: ["@ingredient1", "@ingredient2"]

Il est aussi possible de gérer les relations facilement :

Les Fixtures

App\Entity\Ingredient:
  ingredient{1..100}:
    name: "<word()>"

App\Entity\Pizza:
  pizza1:
    name: "Régina"
    price: 8.8
    ingredients: ["@ingredient1", "@ingredient2"]

Il est aussi possible de gérer les relations facilement :

Ici nous attachons 2 ingrédients à notre entité pizza : @ingredient1 et @ingredient2

Les Fixtures

App\Entity\Ingredient:
  ingredient{1..100}:
    name: "<word()>"

App\Entity\Pizza:
  pizza1:
    name: "Régina"
    price: 8.8
    ingredients: ["@ingredient*"]

Il est aussi possible de gérer les relations facilement :

Il est aussi possible de choisir un ingrédient aléatoire en utilisant l'étoile : *

Repository

Le QueryBuilder

Repository

Le QueryBuilder

Afin de pouvoir récupérer des données de manière optimal doctrine à mis en place un outil : le QueryBuilder

 

Cette outil s'utilise à l'intérieur de nos repository et permet de récupérer des données filtré et ordonné

Repository

Le QueryBuilder

Pour utiliser un QueryBuilder rien de plus simple :

<?php

namespace App\Repository;

// use ...

class PizzaRepository extends ServiceEntityRepository
{
    // ...

    public function findLastTen(): array
    {
        $queryBuilder = $this->createQueryBuilder('pizza');

        return $queryBuilder
            ->orderBy('pizza.id', 'DESC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult();
    }
}

Repository

Le QueryBuilder

<?php

namespace App\Repository;

// use ...

class PizzaRepository extends ServiceEntityRepository
{
    // ...

    public function findLastTen(): array
    {
        $queryBuilder = $this->createQueryBuilder('pizza');

        return $queryBuilder
            ->orderBy('book.id', 'DESC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult();
    }
}

Création d'un QueryBuilder

Repository

Le QueryBuilder

<?php

namespace App\Repository;

// use ...

class PizzaRepository extends ServiceEntityRepository
{
    // ...

    public function findLastTen(): array
    {
        $queryBuilder = $this->createQueryBuilder('pizza');

        return $queryBuilder
            ->orderBy('book.id', 'DESC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult();
    }
}

Chaque QueryBuilder doit avoir un nom de votre choix ici: 'pizza'

Repository

Le QueryBuilder

<?php

namespace App\Repository;

// use ...

class PizzaRepository extends ServiceEntityRepository
{
    // ...

    public function findLastTen(): array
    {
        $queryBuilder = $this->createQueryBuilder('pizza');

        return $queryBuilder
            ->orderBy('book.id', 'DESC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult();
    }
}

Ajout d'un order by. Le premier paramètre constitue le champ et le second la diréction

Repository

Le QueryBuilder

<?php

namespace App\Repository;

// use ...

class PizzaRepository extends ServiceEntityRepository
{
    // ...

    public function findLastTen(): array
    {
        $queryBuilder = $this->createQueryBuilder('pizza');

        return $queryBuilder
            ->orderBy('book.id', 'DESC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult();
    }
}

On limite les résultats à seulement 10

Repository

Le QueryBuilder

<?php

namespace App\Repository;

// use ...

class PizzaRepository extends ServiceEntityRepository
{
    // ...

    public function findLastTen(): array
    {
        $queryBuilder = $this->createQueryBuilder('pizza');

        return $queryBuilder
            ->orderBy('book.id', 'DESC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult();
    }
}

Génération de la requête SQL

Repository

Le QueryBuilder

<?php

namespace App\Repository;

// use ...

class PizzaRepository extends ServiceEntityRepository
{
    // ...

    public function findLastTen(): array
    {
        $queryBuilder = $this->createQueryBuilder('pizza');

        return $queryBuilder
            ->orderBy('book.id', 'DESC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult();
    }
}

Récupération des données sous forme de tableaux d'entité Pizza

Repository

Le QueryBuilder

<?php

namespace App\Repository;

// use ...

class PizzaRepository extends ServiceEntityRepository
{
    // ...

    public function findLastTen(): array
    {
        $queryBuilder = $this->createQueryBuilder('pizza');

        return $queryBuilder
            ->orderBy('book.id', 'DESC')
            ->setMaxResults(10)
            ->getQuery()
            ->getResult();
    }
}

ATTENTION : Une méthode de répository doit toujours commencer par find

Repository

Le QueryBuilder

Afin de se protéger des injéctions SQL, le query builder possède un système de paramètres nommé :

$queryBuilder
    ->andWhere('pizza.name LIKE :name')
    ->setParameter('name', '%Reg%');

Repository

Le QueryBuilder

$queryBuilder
    ->andWhere('pizza.name LIKE :name')
    ->setParameter('name', '%Reg%');

Ajout d'une condition where. Ici si le text contient le paramètre données

Repository

Le QueryBuilder

$queryBuilder
    ->andWhere('pizza.name LIKE :name')
    ->setParameter('name', '%Reg%');

Nous déclarons un paramètre avec la syntax :name

Repository

Le QueryBuilder

$queryBuilder
    ->andWhere('pizza.name LIKE :name')
    ->setParameter('name', '%Reg%');

Nous remplaçons le paramètre :name par "%Reg%"

Graçe à ce remplacement, Doctrine sera capable de sécuriser le paramètre. Aucunes injéction SQL n'est possible

Repository

Le QueryBuilder

Il est aussi possible de faire des conditions et autre sur une relation !

$queryBuilder
    ->leftJoin('pizza.ingredients', 'ingredient')
    ->andWhere('ingredient.name = :name')
    ->setParameter('name', 'Tomate');

Repository

Le QueryBuilder

$queryBuilder
    ->leftJoin('pizza.ingredients', 'ingredient')
    ->andWhere('ingredient.name = :name')
    ->setParameter('name', 'Tomate');

Ici nous joignons les ingrédients de pizza en spécifiant un alias : "ingredient"

Repository

Le QueryBuilder

$queryBuilder
    ->leftJoin('book.ingredients', 'ingredient')
    ->andWhere('ingredient.name = :name')
    ->setParameter('name', 'Tomate');

Maintenant nous pouvons rajouter une condition sur le nom d'ingrédient

Repository

Le QueryBuilder

$queryBuilder
    ->leftJoin('book.ingredients', 'ingredient')
    ->andWhere('ingredient.name = :name')
    ->setParameter('name', 'Tomate');

Remplacement du paramètre nom afin d'éviter les ninjéctions SQL

Repository

Le QueryBuilder

$queryBuilder
    ->leftJoin('book.ingredients', 'ingredient')
    ->andWhere('ingredient.name = :name')
    ->setParameter('name', 'Tomate');

Ici nous pouvons lire la requête comme ceci: Récupére les pizza dont l'un des ingrédients est nommé '"Tomate"

Made with Slides.com