Skip to content

Symfony 5

Symfony 5

Par Nicolas Dewaele / RSSI - Esiee Paris / adminrezo.fr / Document sous licence CC BY-SA

Présentation

Présentation

Qu'est-ce que Symfony

  • Framework PHP
  • Un historique déjà long (2005)
  • Français 🐓
  • Très populaire
  • Très complet
  • Orienté Objet
  • MVC

Note: Frameworks PHP : Laravel (plus léger, à la mode, plus permissif sur le code), CodeIgniter (léger aussi).

Ressources

Avantage d'un framework

  • Beaucoup de composants prêts à l'emploi
  • Pas besoin d'écrire soi-moi du code sans valeur ajoutée
  • Vous oblige à respecter les bonnes pratiques
  • La communauté
  • La documentation
  • Le support à long terme

Note: Organisation propre des fichiers et des Namespace Plus facile à relire pour quelqu'un d'extérieur qui connaît le framework

Inconvénients d'un framework

  • Une certaine lourdeur
  • Absence de maîtrise de la totalité de son code
  • Temps d'apprentissage ou de passage d'un framework à un autre

Note: Compliqué de comprendre toutes les briques du framework et de les implémenter comme il faut.

Un workflow de projet Symfony

  1. Installation de l'environnement de dev
  2. Modélisation du projet en UML
  3. Création du modèle avec Doctrine
  4. Création du contrôleur, des routes, des templates
  5. Création du frontend
  6. Utilisateurs et sécurité
  7. Déploiement en prod

Installation et premier projet

Let's Go

Installation

  • Pas besoin de serveur web (Symfony en intègre un)
  • Installer PHP et Git
  • Sous Linux :
apt install php git

Note: Symfony intègre son propre serveur Web pas de Apache ou Nginx Pour Windows, on télécharge le binaire symfony et on l'utilise en CLI pour faire du 'symfony new', 'symfony ...'

Premier projet

# Vérifier que tout est bon :
symfony check:requirements
# Démarrer votre projet
symfony new TodoList
# Lancer le serveur
symfony server:start

Lancer votre navigateur sur https://localhost:8000

Victory

Eléments d'architecture de Symfony

MVC

MVC : La vue

Vue

  • Echanges avec les utilisateurs finaux
  • Requêtes/réponses HTTP
  • Routes
  • Templates avec Twig
  • Le frontend (faire des trucs jolis) avec Encore

MVC : Le contrôle

Control

  • Le moteur de votre application, là où est l'intelligence
  • La configuration
  • La sécurité

MVC : Le modèle

Model

  • Accès à la persistance
  • Symfony utilise Doctrine comme ORM et DBAL (Comparable à Hibernate dans Java)
  • Associé à une base SQLite (petits projets) ou PostgreSql ou encore MariaDB
  • Doctrine a son propre langage DQL en plus du SQL
  • Modélisé en UML

Note: ORM (Object Relation Mapper) = Interface entre le programme et la base de données pour ne montrer que des objets alors qu'on utilise une base de données relationnelle. DBAL (DB Abstraction Layer) = Couche d'abstraction pour accéder à la base de données. Rend le framework agnostique quant à la base de données utilisée. Doctrine DBAL est elle-même basée sur PDO. PDO = Couche d'abstraction interne à PHP pour accéder à la base de données de manière plus sécurisées que les méthodes mysql_machin et mysqli_bidule. (Requêtes préparées, ...)

Console

Console

La console (CLI) permet d'automatiser beaucoup de tâches. Son utilisation est très pratique mais facultative.

# Liste toutes les options de la console :
php bin/console
# Commandes liées au maker bundle :
php bin/console make:...
# Commandes liées à Doctrine :
php bin/console doctrine:...
# etc.

Twig

Twig

  • Moteur de template PHP, fortement lié à Symfony.
  • Inspiré de Jinja pour Python
  • Fonctionnalités de templating avancées

Doctrine

Doctrine

  • Librairies utilisées par Symfony pour la persistance.
  • Fournit l'ORM et le DBAL.
  • Fournit un langage de requête (DQL)
  • Agnostique quand au SGBD utilisé.

Bundles

Bundle

Flex

Flex

  • Composant qui permet d'installer facilement des bundles
  • Surcharge composer en utilisant des recettes de la communauté.
  • Active automatiquement les bundles dans config/bundles.php

Quiz #1

  1. Qu'est-ce qu'un framework ?
  2. Citez les avantages et inconvénients d'un framework ?
  3. Citez les avantages et inconvénients de Symfony ?
  4. Que trouve-t-on dans le répertoire src/ ?
  5. Que trouve-t-on dans le répertoire public/ ?
  6. Doit-on obligatoirement utiliser la console ?
  7. Qu'est-ce qu'un bundle ?
  8. Qu'est-ce que le design pattern MVC ?
  9. Comment fonctionne l'architecture MVC dans Symfony ?
  10. Citez les grandes étapes de création d'un projet Symfony ?

Les routes

route

Principe du routage

  • Le script public/index.php est le seul à être public
  • Le routage permet de :
  • définir une liste d'URLs et de paramètres disponibles.
  • rediriger la requête client vers le bon contrôleur.

Routes utilisant les annotations

  • Il existe plusieurs méthodes de définitions de routes
  • La plus moderne et simple est celle des annotations
  • Pour SF < 5.2, elle nécessite le bundle symfony/annotations

Routes utilisant les annotations

Exemple dans un contrôleur :

class ConsummerController extends Controller
{
    /**
     * @Route("/consummer/add", name="consummer_add")
     */
    public function add()
    {
       ...

Préfixes de routes

/**
 * @Route("/consummer")
 */
class ConsummerController extends Controller
{
    /**
     * @Route("/add", name="consummer_add")
     * Route accessible par /consummer/add
     */
    public function add()

Routes avec paramètres

    /**
     * @Route("/blog/{page}", name="blog_page")
     */
    public function view($page)
    {
      ...

Restrictions sur les paramètres

Requirements (expressions PCRE) :

Exemples :

    /**
     * @route("/blog/{page}", requirements={"page"="\d+"}, name="blog_page")
     */
    /**
     * @route("/blog/{page<\d+>}", name="blog_page")
     */
    /**
     * @route("/blog/{page}", requirements={"page"="[0-9]+"}, name="blog_page")
     */

Paramètres facultatifs

    /**
     * @Route("/blog/{year}/{month}", name="blog_page", requirements={"year"="\d{4}","month"="\d{2}"}, defaults={"month": 01})
     */

Attention : Vous pouvez avoir plusieurs paramètres facultatifs mais après un paramètre facultatif, tous les paramètres doivent être facultatifs.

Exemple : /article/{category}/{tag}/{orderby}

Méthodes HTTP

    /**
     * @Route("/lenom", methods={"GET","POST"})
     */

Conditions

    /**
     * @Route(
     *     "/user/add",
     *     name="user_add",
     *     condition="context.getMethod() in ['GET', 'POST'] and request.headers.get('User-Agent') matches '/firefox/i'"
     * )
     */

Debug des routes

php bin/console debug:router

Contrôleur

Controller

Request / Response

  • Utilise les classes Request et Response
  • L'objet Request contient des paramètres envoyés par le client
  • Pour qu'un contrôleur retourne une réponse au client, il faut :
  • Instancier la classe Response
  • Retourner une réponse

Request

<?php
// src/Controller/HelloController.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 AdvertController extends Controller
{
    /**
     * @Route("/hello/{name}", name="hello")
     */
    public function view($id, Request $request)
    {
    }
}

Request récupération de paramètres

  • Request récupère des paramètres :
  • Depuis les routes : /hello/toto
  • à l'ancienne : /hello?name=toto
public function view($id, Request $request)
{
    $name = $request->query->get('name'); 
}

Méthodes Request

Méthodes Request
GET $request->query
POST $request->request
COOKIE $request->cookies
SERVER $request->server
HEADERS $request->headers
$request->isMethod('POST') La méthode est-elle POST ?
$request->isXmlHttpRequest() Est-ce une requête Ajax ?

Response côté PHP

Pour envoyer une réponse on va utiliser les vues donc les templates Twig.

public function view($id, Request $request)
{
    $name = $request->query->get('name'); 
    return $this->render('hello.html.twig', ['name' => $name]);
}

Response côté template Twig

{# templates/hello.html.twig #}

<!DOCTYPE html>
<html>
<head>
  <title>Hello {{ name }}</title>
</head>
<body>
  <h1>Hello {{ name }} !</h1>
</body>
</html>

Réponses HTTP

  $response->setContent("Ceci est une page d'erreur 404");
  $response->setStatusCode(Response::HTTP_NOT_FOUND);
  return $response;

Response de redirection

<?php

public function viewHello($name)
{
  return $this->redirectToRoute('start');
}

Sessions

Symfony fournit un objet Session permettant de manipuler la session utilisateur :

<?php
...
use Symfony\Component\HttpFoundation\Session\SessionInterface;
...
public function viewHello($name, SessionInterface $session)
{
    $name = $session->get('name');
    $consummerID = 123;
    $session->set('consummerID', $consummerID);
    return $this->render('hello.html.twig', ['name' => $name, 'consummerID'=$consummerID]);
}

Templates avec Twig

Twig

Twig

  • Evite de mettre du code (PHP) dans l'affichage (HTML)
  • Héritage de templates
  • Sécurité des variables

Utilisation depuis un contrôleur

Envoi de variable d'un contrôleur à un template

return $this->render('hello.html.twig', ['name' => $name, 'consummerID'=$consummerID]);

Utilisation d'un template depuis le contrôleur

$body = $this->renderView('mail.html.twig', ['name' => $name]);
mail('destinataire@domain.com','Bienvenue',$body);

Utilisation dans le template

  • Le template est une page HTML
  • On y ajoute des instructions :
  • {{ unevariable }}
  • {{ unevariable | unfiltre }}
  • {% uneinstruction %} du type if, for, set, ...
  • {# uncommentaire #}
  • CheatSheet

Héritage

L'intérêt d'un moteur de template est de créer une structure de base : - Head - Haut de page - Barres de navigation - Pied de page - ...

Et de ne modifier que le contenu de chaque page.

Héritage en pratique

La page de base :

{# templates/base.html.twig #}

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>{% block title %}Ma page de base{% endblock %}</title>
</head>
<body>
    {% block body %}{% endblock %}
</body>
</html>

Héritage en pratique

Une page qui hérite de la base :

{# templates/hello.html.twig #}

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

{% block title %}{{ parent() }} - Hello{% endblock %}
{% block body %}Bonjour {{ name }}{% endblock %}

Inclusion

On peut aussi inclure un template dans un autre pour ne pas réécrire du code :

{# templates/hello.html.twig #}
{% include("template/form.html.twig") %}

Rendering

On peut aussi inclure un contrôleur dans un template pour pouvoir profiter de ses variables :

{# templates/hello.html.twig #}
{% render(controller("controller.html.twig") %}

Quiz #2

  1. Comment fonctionne le routage dans Symfony ?
  2. Quels fichiers sont publics ?
  3. Comment vérifier la validité d'un paramètre d'une route ?
  4. Comment rendre un paramètre facultatif ?
  5. Quelles sont les données d'entrées d'un contrôleur ?
  6. Quelles sont les données de sortie d'un contrôleur ?
  7. Comment définir une variable dans la session de l'utilisateur ?
  8. Quelle balise Twig définit une variable ?
  9. Qu'est-ce que l'héritage dans Twig ?
  10. Comment faire une inclusion dans un template ?

Doctrine

Doctrine

Entités

ORM

  • Doctrine fournit l'accès aux objets en base de données.
  • Historiquement pas d'objets en BDD
  • Aujourd'hui on ne requête plus directement, on fait appel à un ORM
  • Doctrine fournit également un langage de requêtes proche du SQL : Le DQL.

Entités

  • Les entités sont des objets qui seront modélisés en base de données avec :
  • Des paramètres
  • Des méthodes d'accès (getters et setters)

Annotations

  • Des annotations décrivent comment l'entité doit-être en BDD
/**
 * @ORM\Entity
 */
class Consummer
{
  ...
  /**
   * @ORM\Column(name="surname", type="string")
   */
  protected $surname;
  ...

Création de la base

Définir le type de base de données dans le fichier .env :

DATABASE_URL="sqlite:///%kernel.project_dir%/var/app.db"

Ajouter les bundles ORM et maker puis créer la base :

composer require symfony/orm-pack
composer require --dev symfony/maker-bundle
php bin/console doctrine:database:create

Utilisation du maker

La commande make:entity vous donne accès à un assistant :

php bin/console make:entity
...

Compléter les entités

Certains paramètres doivent être calculés en live et pas stockés en base.

Il faut alors compléter l'entité avec de nouvelles fonctions get.

Constructeur

Permet de définir des paramètres par défaut à l'initialisation de l'entité.

  public function __construct()
  {
    // Par défaut, la date de l'annonce est la date d'aujourd'hui
    $this->date = new \Datetime();
  }

Création/Màj du schéma

Attention avec schema:update il n'y a pas de retour possible :

# Vérifier les modifications faites sur le schéma
php bin/console doctrine:schema:update --dump-sql
# Appliquer les changements (à refaire à chaque modification d'entités)
php bin/console doctrine:schema:update --force

Migrations

Les migrations sur des points d'avancements des modifications de votre schéma :

# Créer une migration complète
php bin/console make:migration
# Créer une migration différentielle par rapport à la dernière faite
php bin/console doctrine:migrations:diff
# Appliquer une migration
php bin/console doctrine:migrations:execute --up MIGRATION-ID
# Appliquer toutes les migrations
php bin/console doctrine:migrations:migrate

Services Doctrines

Doctrine fournit des services pour accéder aux entités : - Doctrine - EntityManager : Permet de faire exécuter des requêtes à Doctrine - Repositories : Permet de récupérer des objets (1 par repository)

Ecrire en base de données

<?php
use Entity\Consummer;
...
class ConsummerController extends Controller
{
  public function addConsummer(Request $request)
  {
    $consummer = new Consummer();
    $consummer->setName('Michou');
    $em = $this->getDoctrine()->getManager();
    // Ouvre une transaction pour écrire en base
    $em->persist($consummer);
    // Commit la transaction
    $em->flush();

Requêter la base de données

Commande tout en un : Récupère le repository Consummer et trouve l'ID $id :

$repository = $this->getDoctrine()
  ->getManager()
  ->getRepository('Consummer::Class')
  ->find($id);

Relations

Les relations entre entités peuvent être de type : - OneToOne - OneToMany - ManyToMany

Propriétaire et inverse

Propriétaire : Celle qui contient la référence à l'autre entité.

Exemple : - Produit 1..N <---> 1..1 Commentaire - Un produit peut avoir un ou plusieurs commentaires. - Un commentaire est associé à un seul produit.

Commentaire aura besoin de l'ID du produit qui lui est associé.

Commentaire est propriétaire, Produit est inverse.

Sens de la relation

Dans une relation unidirectionnelle, seul le propriétaire peut obtenir l'entité associée.

On peut obtenir les commentaires associés à un produit :

$comment->getProduct();

Par contre on ne peut pas faire directement ceci, il faudrait une relation bidirectionnelle :

$product->getComments();

OneToOne

OneToOne

Si un auteur est lié à un avatar et un avatar à un auteur :

<?php
// src/Entity/Author.php

/**
 * @ORM\Entity
 */
class Author
{
  /**
   * @ORM\OneToOne(targetEntity="App\Entity\Avatar", cascade={"persist", "remove"})
   */
  private $name;
  ...
}
OneToOne
  • Seule l'entité propriétaire indique la relation.
  • Dans cet exemple, il n'y a rien dans l'entité Avatar.
Non facultative
  • Une relation est par défault facultative (0..N et non pas 1..N)
  • Pour la rendre non facultative :
  /**
   * ...
   * @ORM\JoinColumn(nullable=false)
   */
Cascade

On peut choisir que la suppression ou l'ajout d'un objet supprime ou ajoute en même temps l'objet de la relation.

Dans cet exemple, si j'ajoute un auteur, j'ajoute son avatar. Et si j'en supprime un, je supprime également l'avatar.

  /**
   * @ORM\OneToOne( ... , cascade={"persist", "remove"})
   */
Getters et Setters

Dans l'entité propriétaire, il faut ajouter les getters et setters :

<?php
// src/Entity/Author.php
...
class Author
{
  ...
  public function setAvatar(Avatar $avatar = null)
  {
    $this->avatar = $avatar;
  }

  public function getAvatar()
  {
    return $this->avatar;
  }
}
Récupérer un attribut d'une relation

Quand Author veut récupérer l'URL de l'avatar :

$avatarurl = $author->getAvatar()->getUrl();

ManyToOne

ManyToOne

Exemple :

  • Produit 1..N <---> 1..1 Commentaire
  • Commentaire sera le propriétaire
Déclaration de la relation
<?php
// src/Entity/Comment.php

...
class Comment
{
  ...
  /**
   * @ORM\ManyToOne(targetEntity="Entity\Product")
   * @ORM\JoinColumn(nullable=false)
   */
  private $product;
  ...
}
Getters et setters
  public function setProduct(Product $product)
  {
    $this->product = $product;
    return $this;
  }
  public function getProduct()
  {
    return $this->product;
  }
Utilisation
TODO

ManyToMany

Exemple :

  • Produit 1..N <---> 1..N Tags
ManyToMany

De manière classique, on crée une table intermédiaire pour résoudre ce type de relation.

La table intermédiaire contiendrait deux colonnes : id_product et id_tag.

Doctrine va le faire automatiquement.

Déclaration dans l'entité propriétaire
<?php
// src/Entity/Product.php

...
class Product
{
  ...
  /**
   * @ORM\ManyToMany(targetEntity="Entity\Tag", cascade={"persist"})
   * @ORM\JoinColumn(nullable=false)
   */
  private $tags;
  ...
}
Tableau de tags

L'objet qu'on va manipuler côté Produit sera une collection de Tags (ArrayCollection).

Getters et setters
<?php
...
use Doctrine\Common\Collections\ArrayCollection;

  ...
  public function __construct()
  {
    $this->tags = new ArrayCollection();
  }
  public function addTag(Tag $tag)
  {
    $this->tags[] = $tag;
  }
  public function removeTag(Tag $tag)
  {
    $this->tags->removeElement($tag);
  }
  public function getTags()
  {
    return $this->tags;
  }
  ...
}

Fixtures

Les fixtures sont des données de test qu'on génère pour la phase de développement.

composer require doctrine/doctrine-fixtures-bundle --dev
# ... Créer les fixtures ...
# puis :
php bin/console doctrine:fixtures:load [--append]
Exemple de fixture
<?php
// src/DataFixtures/TagFixtures.php
namespace App\DataFixtures;

use App\Entity\Tag;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;

class TagFixtures extends Fixture
{

    public function load(ObjectManager $manager)
    {
        for ($i = 0; $i < 20; $i++) {
            $tag = new Tag();
            $tag->setName('Tag test$i');
            $manager->persist($tag);
        }
        $manager->flush();

Requêtes avec Doctrine

DQL ou QueryBuilder

  • Quand on doit récupérer des données, on utilise le repository de l'entité concernée
  • Le DQL est le langage de requêtes de Doctrine hérité du SQL
  • Le QueryBuilder est une Classe qui construit des requêtes
  • On peut utiliser le QueryBuilder pour facilement modifier des requêtes

Méthodes simples du repository

  • find($id)
  • findAll()
  • findBy(critères)
  • findOneBy(critères)
  • findByBidule("valeur") par exemple findByFirstname("Jojo")
  • findOneByBidule("valeur")

QueryBuilder côté Repository

<?php
// src/Repository/ConsummerRepository.php
  ...
  public function findByName($name)
  {
    $qb = $this->createQueryBuilder('c');
    $qb->where('c.name = :name');
    $qb->setParameter('name', $name);
    return $qb->getQuery()->getResult();
  }

QueryBuilder côté contrôleur

<?php
// src/Controller/ConsummerController.php
...
  public function getByName($name)
  {
    $repo = $this
      ->getDoctrine()
      ->getManager()
      ->getRepository('App\Repository\Consummer')
    ;

    $myConsummer = $repo->findByName($name);
  }
...

DQL côté Repository

<?php
// src/Repository/ConsummerRepository.php
  ...
  public function findByNameDQL()
  {
    $query = $this->_em->createQuery('SELECT c FROM App\Repository\Consummer c WHERE c.name = $name');
    return $query->getResult();
  }

DQL côté contrôleur

<?php
// src/Controller/ConsummerController.php
...
  public function getByName($name)
  {
    $repo = $this
      ->getDoctrine()
      ->getManager()
      ->getRepository('App\Repository\Consummer')
    ;

    $myConsummer = $repo->findByNameDQL($name);
  }
...

Quiz #3

Formulaires

Form

Créer un formulaire

On va utiliser l'objet FormBuilder pour construire un formulaire à partir d'un objet existant.

Génération du formulaire

<?php
// src/Controller/ConsummerController.php
...
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
...
  public function addConsummer(Request $request)
  {
    $consummer = new Consummer();
    $fb = $this->get('form.factory')->createBuilder(FormType::class, $consummer);
    $fb
      ->add('surname', TextType::class)
      ->add('firstname', TextType::class)
      ->add('dateOfBirth', DateType::class)
      ->add('save', SubmitType::class)
    ;
    $form = $formBuilder->getForm();
    return $this->render('templates/consummer/add.html.twig', array(
      'form' => $form->createView(),
    ));
  }
}

FormBuilder côté template

Twig peut récupérer un formulaire complet simplement avec la variable form qu'on récupère du contrôleur :

{{ form }}

Types de champs de formulaire

Symfony fournit bon nombre de types de champs de formulaire. Ces types doivent renvoyer une donnée du même type que défini dans l'entité.

Choisissez le type le plus adapté :

https://symfony.com/doc/current/reference/forms/types.html

Soumission du formulaire

<?php
// src/Controller/ConsummerController.php
...
  public function addConsummer(Request $request)
  {
  ...
    if ($request->isMethod('POST')) {
      $form->handleRequest($request);
      if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();
        $em->persist($consummer);
        $em->flush();
        $request->getSession()->getFlashBag()->add('notice', 'Nouvel utilisateur ajouté.');
        return $this->redirectToRoute('consummer_list');
      }
    }
    return $this->render('templates/consummer/add.html.twig', array(
      'form' => $form->createView(),
    ));
    ...

Valeurs par défaut

Quand on instancie un objet, si on lui définit un paramètre, cela devient une valeur par défaut dans le formulaire :

<?php
$consummer = new Consummer;
$consummer->setGender("Homme");
$formBuilder = $this->get('form.factory')->createBuilder(FormType::class, $consummer);
...

Templates avancés

Nous avons vu qu'on peut utiliser la variable Twig {{ form }} qui contient tout le formulaire.

On peut aussi récupérer le formulaire champs par champs :

  <div class="form-group">
    {{ form_label(form.surname, "Nom de famille", {'label_attr': {'class': 'col-sm-2 control-label'}}) }}
    {{ form_errors(form.surname) }}
    <div class="col-sm-10">
      {{ form_widget(form.surname, {'attr': {'class': 'form-control'}}) }}
    </div>
  </div>

Fonctions Twig liées aux formulaires

  • form_start() affiche <form>
  • form_errors() affiche les erreurs du champ donné en argument.
  • form_label() affiche le label du champ donné en argument.
  • form_widget() affiche le champ lui-même.
  • form_row() affiche le label, les erreurs et le champ en même temps.
  • form_rest() affiche tous les champs restant (y compris le champ CSRF).
  • form_end() affiche </form>

To be continued ...

Le frontend

Frontend

Encore

Webpack permet de packager des scripts JS et des ressources statiques (CSS, images, HTML).

C'est très utile mais complexe à prendre en main.

Symfony fournit la librairie "Encore" qui simplifie son utilisation.

Installation

  • Nécessite Yarn
  • Installation avec Composer :
composer require symfony/webpack-encore-bundle
yarn install

Fichiers

  • webpack.config.js
  • assets/js/app.js
  • assets/css/app.css

Dans le fichier webpack.config.js on trouve l'appel aux scripts JS :

...
.addEntry('app', './assets/js/app.js')
...

Utilisation

On peut travailler directement sur les fichiers assets/js/app.js et assets/scss/app.scss et à chaque modification, il faut recompiler avec Yarn :

yarn encore dev # --watch si on veut compiler en temps réél
yarn encore production

Ce qui génère : - public/build/app.js - public/build/app.css - public/build/runtime.js

Appel

Dans Twig, on appelle les assets de cette manière :

...
        {% block stylesheets %}
            {{ encore_entry_link_tags('app') }}
        {% endblock %}
...
        {% block javascripts %}
            {{ encore_entry_script_tags('app') }}
        {% endblock %}
...

Images

Nous placerons les images dans assets/images et Encore les copiera dans images/nomdelimage.extension :

// webpack.config.js

Encore
    // ...
    .setOutputPath('public/build/')

    .copyFiles({
        from: './assets/images',
        to: 'images/[path][name].[ext]',

        pattern: /\..webp.webp|jpeg)$/
     })

JQuery

Activer Jquery :

// assets/js/app.js
// ...
const $ = require('jquery');
// ...

Installer JQuery avec Yarn :

yarn add jquery
yarn encore dev

Fork Awesome

Activer Fork Awesome :

// assets/css/app.scss
// ...
@import "forkawesome/scss/forkawesome.scss";
// ...

Installer FontAwesome avec Yarn :

yarn add fork-awesome
yarn encore dev

SCSS

Activer Sass dans le Webpack :

// webpack.config.js
Encore
    // ...
    .enableSassLoader()

Appeler le fichier scss et non pas css :

// assets/js/app.js
...
require('../css/app.scss');
...

Installer Sass et recompiler :

yarn add sass node-sass --dev
yarn encore dev

Quiz #4

La sécurité

Sécurité

Fonctionnement

Symfony fournit un système d'authentification et d'autorisation très simple à mettre en oeuvre.

  • Le provider définit un système d'authentification
  • Le firewall définit des restrictions en fonction de critères
  • Le contrôle d'accès permet d'affecter des droits à des rôles

Processus d'authentification

  1. Un utilisateur veut accéder à une ressource protégée
  2. Le firewall redirige l'utilisateur vers la page de login
  3. L'utilisateur s'identifie
  4. Le firewall authentifie l'utilisateur
  5. L'utilisateur authentifié redemande la ressource
  6. Le contrôle d'accès autorise ou non l'accès

Installation du bundle

composer require symfony/security-bundle

security.yaml

  • Le firewall Symfony liste les autorisations
  • Il lit les règles dans l'ordre.
  • Dès qu'il voit une règle correspondant à la demande, il applique la politique associée.
# config/packages/security.yaml
security:

    # Cette section définit sur quoi on s'authentifie
    # https://symfony.com/doc/current/security/auth_providers.html
    providers:
        ...

    # Cette section définit des restrictions selon certains critères
    firewalls:
        ...

    # Cette section définit des droits selon les rôles
    access_control:
        ...

Authentification en base de données

La console permet de créer un système d'authentification simple :

# Crée l'entité user
php bin/console make:user

# Crée une migration et l'applique
php bin/console make:migration
php bin/console doctrine:migrations:migrate

# Crée un formulaire d'authentification
# - src/Security/LoginFormAuthenticator.php
# - src/Controller/SecurityController.php
# - templates/security/login.html.twig
# - config/packages/security.yaml
php bin/console make:auth

Provider

# config/packages/security.yaml
...
    providers:
        app_user_provider:
            entity:
                class: App\Entity\User
                property: username
...

Firewall

Le firewall restreint les accès selon ces critères : - Chemin - Hôte - Méthode HTTP - Service

# config/packages/security.yaml
...
    firewalls:
        main:
            anonymous: ~
            form_login:
                login_path: login
                check_path: login
            logout:
                path: logout
...

Access control

# config/packages/security.yaml
...
    access_control:
        # require ROLE_ADMIN for /admin*
        - { path: '^/admin', roles: ROLE_ADMIN }
        # require ROLE_ADMIN or IS_AUTHENTICATED_FULLY for /product*
        - { path: '^/product', roles: [IS_AUTHENTICATED_FULLY, ROLE_ADMIN] }
...

Quiz #5

Déploiement en production

Deployment

Le workflow

  1. Vérifier les prérequis
  2. Télécharger le code (idéalement avec Git)
  3. Modifier les variables d'environnement
  4. Installation/mise à jour des dépendances composer
  5. Installation des dépendances Encore
  6. Nettoyer le cache
  7. Initialiser la base
  8. Rsync du bouzin dans le bon répertoire et droits Unix

Variables d'environnement

Le fichier .env.local surcharge le fichier .env

.env.local est dans le gitignore donc n'est pas synchronisé.

On peut donc lui indiquer qu'on est en prod :

APP_ENV=prod
APP_DEBUG=0

Composer

Installation des dépendances composer :

composer install --no-dev --optimize-autoloader

Encore

Installation des dépendances Yarn Encore :

yarn install && yarn encore production

Vider le cache

APP_ENV=prod APP_DEBUG=0 php bin/console cache:clear

Initialiser/mettre à jour la base

./bin/console doctrine:database:create
./bin/console doctrine:migration:migrate

Déploiement

Rsync du dépôt Git dans un répertoire avec les bons droits Unix

A toi de jouer