Kaherecode

Créer une application web avec Symfony - Les vues

Mamadou Aliou Diallo
@alioukahere 25 sept. 2021
185

Hey salut, bienvenue dans cette série sur Symfony, dans la dernière partie, nous avons créé nos routes et contrôleurs, nous avons aussi vu comment retourner du texte depuis un contrôleur en utilisant la classe Response de Symfony. Dans ce tutoriel, nous allons encore aller plus loin, nous allons parler des vues et voir comment retourner une page HTML.

Tu te rappelles je t'avais dit que Symfony repose sur l'architecture MVC (Modèle - Vue - Contrôleur), nous avons déjà parler du contrôleur qui prend une requête la traite et construit la vue pour la renvoyer à l'utilisateur, nous allons donc parler de la vue aujourd'hui.

Nous allons continuer à travailler sur le codebase du dernier tutoriel, si tu ne l'as pas, il est disponible sur Github, tu peux le cloner en utilisant git:

$ git clone https://github.com/kaherecode/symfony-ticketing-app.git
$ cd symfony-ticketing-app
$ git checkout controllers
$ composer install

La troisième ligne va te permettre de te placer exactement sur le code nécessaire pour continuer sur ce tutoriel et ensuite tu installes les dépendances avec composer.

Le moteur de template Twig

Symfony utilise un moteur de template qui s'appelle Twig. L’intérêt d'utiliser un moteur de template c'est la clarté du code, on écrira pas du PHP dans du HTML. Par exemple, disons que nous avons un tableau d'objet utilisateur et on veut afficher le nom de chaque utilisateur dans une liste ordonnée, en PHP, on aurait fait:

<ol>
    <?php foreach ($users as $user) : ?>
        <li><?= $user->getName() ?></li>
    <?php endforeach; ?>
</ol>

Et avec Twig, voilà ce qu'on aurait fait:

<ol>
    {% for user in users %}
        <li>{{ user.name }}</li>
    {% endfor %}
</ol>

Alors, ce n'est pas vraiment évident avec ce petit bout de code, mais maintenant imagine le cas d'un grand projet, ça aurait beaucoup plus complexe.

Une autre particularité de Twig, c'est sa rapidité, tous les templates sont compilés en PHP et mis en cache, et seulement quand tu changes le code d’un template, Twig le recompile et le remet encore en cache, ce qui est très pratique en production.

Pour utiliser Twig, il faut retenir ces trois syntaxes:

En Symfony (depuis la version 4), les templates se trouvent dans le dossier templates qui se trouvent à la racine et les fichiers Twig ont une extension .twig

Si tu ouvres le dossier templates, tu verras deux sous dossiers core et event et un fichier base.html.twig. A l’intérieur du sous dossier core se trouve un fichier index.html.twig, si tu te rappelles bien, ce fichier et le dossier core ont été généré lorsque nous avons créé la classe CoreController, c'est aussi le cas pour le sous dossier event avec la classe EventController. Pour le moment, on laisse tomber le fichier base.html.twig, tu ne le supprimes pas, on le garde juste à côté un instant.

Ouvre donc le fichier index.html.twig qui se trouve dans templates/core, nous allons remplacer son contenu par ce qui suit:

{# templates/core/index.html.twig #}
<h1>Hello toi dev Symfony, ceci est une vue twig! Bravo!</h1>

Nous avons notre vue, il faut maintenant faire le lien avec le contrôleur pour lui dire de retourner cette page à chaque fois que l'utilisateur visite la page d'accueil de notre site, nous allons donc faire cela dans la fonction homepage() de la classe CoreController.php.

Pour rappel, voici le contrôleur homepage():

<?php // src/Controller/CoreController.php

namespace App\Controller;

// ...

class CoreController extends AbstractController
{
    /**
     * @Route("/", name="homepage")
     */
    public function homepage(): Response
    {
        return new Response("
            <h1>Hello toi dev Symfony,</h1>
            <h2>Bienvenue sur Kaherecode!</h2>
        ");
    }

    // ...
}

Le contrôleur retourne un objet Response pour l'instant, vu que nous l’avons déjà créer une vue, nous allons donc l'utiliser:

<?php // src/Controller/CoreController.php

namespace App\Controller;

// ...

class CoreController extends AbstractController
{
    /**
     * @Route("/", name="homepage")
     */
    public function homepage(): Response
    {
        return $this->render('core/index.html.twig');
    }

    // ...
}

Si tu te rends maintenant sur la page d'accueil de notre site, tu dois avoir la page:

https://imgur.com/ozCXKzu.jpg Rendu avec Twig

Et voilà, on a notre première vue, c'est vraiment fort. Nous utilisons la méthode render() de la classe AbstractController de Symfony, et nous lui envoyons en paramètre la vue à afficher, le chemin vers notre template. Symfony recherche les templates dans le dossier templates qui se trouve à la racine de notre projet, donc nous utilisons le chemin en fonction de ce dossier.

Voyons maintenant comment envoyer un paramètre à une vue twig, comme dans le cas de notre contrôleur EventController::show($id) qui va afficher l'id que nous recevons depuis l'URL sur la page. Voilà actuellement le contenu de la fonction show():

<?php // src/Controller/EventController.php

namespace App\Controller;

// ...

class EventController extends AbstractController
{
    /**
     * @Route("/events/{id}", name="show_event", requirements={"id"="\d+"})
     */
    public function show($id): Response
    {
        return new Response("<h1>Affichage de l'événement {$id}.</h1>");
    }

    // ...
}

Nous allons donc modifier cette fonction pour retourner une vue twig et envoyer la variable $id à cette vue:

<?php // src/Controller/EventController.php

namespace App\Controller;

// ...

class EventController extends AbstractController
{
    /**
     * @Route("/events/{id}", name="show_event", requirements={"id"="\d+"})
     */
    public function show($id): Response
    {
        return $this->render('event/show.html.twig', ['event_id' => $id]);
    }

    // ...
}

Nous utilisons toujours la méthode render(), mais cette fois ci, nous lui envoyons un deuxième paramètre, un tableau associatif dans lequel on a une clé event_id qui a comme valeur la variable $id que nous récupérons depuis l'URL de la requête. À quoi sert ce tableau? À envoyer des paramètres à notre vue, tu te rappelles de ce que fait le contrôleur? Prendre une requête, aller chercher les modèles dont la vue a besoin, construire la vue puis retourner une réponse au client. Ici, prend la variable $id comme un objet que nous venons de récupérer en base de données, nous voulons donc afficher cet objet dans notre vue Twig, il faut l'envoyer à la vue, on utilise donc ce deuxième paramètre pour envoyer l'objet ($id) au template show.html.twig.

Nous allons maintenant créer le fichier event/show.html.twig dans le dossier templates:

{# templates/event/show.html.twig #}
<h1>Affichage de l'événement {{ event_id }} dans une vue twig.</h1>

Si je me rends maintenant sur l'URL https://127.0.0.1:8000/events/35, j'ai la page:

https://imgur.com/MHY6t1T.jpg

Et voilà! Dans la vue, nous utilisons la clé du tableau que nous avons défini comme deuxième paramètre de la méthode render() du contrôleur, et chaque clé dans ce tableau représente le nom d'une variable dans la vue associée. En twig, nous utilisons {{ nom_variable }} pour afficher une variable.

Ça y est, un peu de HTML, du CSS par ci, et on a notre application.

Jusque là, nos pages sont, comment dire, moches, mais nous allons y travailler, dans un petit instant. Avant, laisse moi te présenter une autre grande fonctionnalité de twig que tu va kiffer.

Héritage de templates

Généralement, sur une application ou site web, nous avons des blocs qui ne changent pas sur toutes les pages, c'est le cas de la barre de navigation et/ou du pied de page. Avec Twig, nous n'allons pas redéfinir ces blocs sur toutes le pages, nous allons définir un template mère qui va contenir l'architecture de base de nos pages et dont les pages de notre site vont hérités. Le template mère va définir des blocs (block) dans lesquels on pourra rajouter du contenu spécifique pour chaque page. Cela fonctionne comme l’héritage en PHP, on peut redéfinir les méthodes publics de la classe mère.

Dans notre cas, nous allons utiliser le fichier templates/base.html.twig comme template mère, il va donc contenir la barre de navigation de notre site et définir un bloc pour le contenu de nos pages.

{# templates/base.html.twig #}
<!doctype html>
<html lang="fr">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    {% block stylesheets %}
        {# Bootstrap CSS #}
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    {% endblock %}

        <title>{% block title %}Symfony Ticketing App{% endblock %}</title>
</head>

<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <a class="navbar-brand" href="{{ path('homepage') }}">Symfony Ticketing App</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#mobileMenu" aria-controls="mobileMenu" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="mobileMenu">
            <ul class="navbar-nav ml-auto mt-2 mt-lg-0">
                <li class="nav-item active">
                    <a class="nav-link" href="{{ path('homepage') }}">Accueil</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">Connexion</a>
                </li>
            </ul>
        </div>
    </nav>

    {% block content %}
        {# Le contenu de chaque page sera ici #}
    {% endblock %}

    {% block scripts %}
        {# jQuery first, then Popper.js, then Bootstrap JS #}
        <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
    {% endblock %}
</body>

</html>

Ce fichier défini le template de base de toutes les pages de notre site, toutes nos pages devront donc hériter de ce fichier. Nous définissons ici 4 blocs (block): titlestylesheetscontent et scripts. Les blocs vont nous servir à définir des sections dans lesquels le template enfant va pouvoir écrire. Tout le contenu qui ne se trouve pas dans un bloc est une méthode privé, on ne peut donc pas le modifier dans les templates enfants, ce qui veut donc dire qu'on ne peut pas modifier le contenu de la balise header dans les templates enfants, parce que ceux-ci ne sont pas définis dans un bloc. Maintenant pour pouvoir voir les modifications, nous allons faire hériter notre page d'accueil (core/index.html.twig) de ce template, nous aurons donc:

{# templates/core/index.html.twig #}

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

<h1>Hello toi dev Symfony, ceci est une vue twig! Bravo!</h1>

On utilise extends comme en PHP aussi. Nous mettons juste le nom du fichier, le chemin de base pour tous les templates c'est le dossier templates, le fichier base.html.twig se trouve directement dans ce dossier, pas dans un sous dossier, donc on ne spécifie aucun chemin ici. Si on actualise la page d'accueil on a:

https://imgur.com/y9Poqqp.jpg

Oouppsss, une erreur! Alors ça dit quoi? "Un template qui hérite d'un autre ne peut pas définir du contenu en dehors d'un bloc", le message d'erreur est très clair, nous affichons la balise h1 en dehors d'un bloc. Le bloc content que nous avons défini dans le fichier base.html.twig va servir à contenir tout le contenu de nos pages, nous allons donc l'utiliser comme ceci:

{# templates/core/index.html.twig #}
{% extends 'base.html.twig' %}

{% block content %}
    <h1>Hello toi dev Symfony, ceci est une vue twig! Bravo!</h1>
{% endblock %}

Si tu actualises maintenant la page d'accueil:

https://imgur.com/n0DrtrU.jpg

Yesssss, nous avons bien la barre de navigation, avec le texte qui se trouve dans le bloc content, t'es un génie!

Et si tu définis un autre bloc en dehors du bloc content, ce dernier ne sera pas afficher. Tu peux par contre définir un bloc dans le bloc content, mais rappelle toi, ça ne sert à rien de juste créer des blocs si ce n'est pas pour les réutiliser dans un template enfant.

Nous allons donc faire la même chose pour le template event/show.html.twig:

{# templates/event/show.html.twig #}
{% extends 'base.html.twig' %}

{% block content %}
    <h1>Affichage de l'événement {{ event_id }} dans une vue twig.</h1>
{% endblock %}

Comme nous l'avons précédemment dit, il est possible de modifier ou d'écraser le contenu de tous les blocs du template parent dans les templates fils. Le bloc title par exemple, permet de définir le titre d'une page, nous avons une valeur par défaut qui est "Symfony Ticketing App", nous voulons maintenant que le titre de nos pages ressemble à ceci "Accueil | Symfony Ticketing App", c'est à dire le nom de la page, puis le nom de l'application. Mais comment faire celà? Il faut redéfinir le bloc title dans le template fils et lui mettre une valeur. Voyons voir:

{# templates/core/index.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}Accueil{% endblock %}

{% block content %}
    <h1>Hello toi dev Symfony, ceci est une vue twig! Bravo!</h1>
{% endblock %}

Je modifie ici le template core/index.html.twig de la page d'accueil, regardons donc ce que ça donne dans le navigateur:

https://imgur.com/4Bu2nAk.jpg

UH OH! On a seulement "Accueil", mais si tu réfléchis tu te dis que c'est normal, on n'a pas demandé à ré-utiliser le contenu du bloc dans le template parent, c'est comme quand tu redéfini le constructeur d'une classe en PHP, si tu veux utiliser le constructeur de la classe mère, il faut le spécifier avec parent::__construct(), ça va aussi être le cas ici, il faut donc faire:

{# templates/core/index.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}Accueil | {{ parent() }}{% endblock %}

{% block content %}
    <h1>Hello toi dev Symfony, ceci est une vue twig! Bravo!</h1>
{% endblock %}

Voilà, nous avons un template base.html.twig dont toutes les pages de notre application vont hériter.

Inclure un template

L'inclusion de template va nous permettre d'injecter un template dans un autre. C'est différent de l'héritage dans le sens ou avec l'héritage, le template fils va hériter de la structure même du template parent, avec l'inclusion, on prend un template qu'on injecte à un emplacement spécifique d'un autre template. Prenons l'exemple d'un formulaire pour éditer un événement, ce formulaire il est le même sur la page d'ajout d'un événement que sur la page pour modifier un événement, pour éviter donc de le redéfinir dans ces deux pages, nous allons sortir juste le formulaire, le mettre dans un fichier et ensuite l'inclure dans ces deux pages. Nous verrons cet exemple plus tard, quand nous parlerons des formulaires. Pour illustrer l'inclusion ici, je vais sortir l'en-tête et le pied de page de nos pages, les balises header et footer dans deux fichiers différents et après les inclures dans le fichier base.html.twig.

Pour organiser un peu le dossier templates, je vais créer un sous dossier includes dans lequel je vais mettre les fichiers à inclure. Je vais donc créer les fichiers includes/_header.html.twig et includes/_footer.html.twig, je précède le nom des fichiers à inclure d'un underscore (_), c'est juste un autre moyen de savoir que ce sont des templates à inclure, j'aime me faciliter la vie, parce que dans quelques mois, je risque de me perdre sur du code que j'ai écris. Voici donc le contenu du fichier includes/_header.html.twig:

{# templates/includes/_header.html.twig #}
<header>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <a class="navbar-brand" href="{{ path('homepage') }}">Symfony Ticketing App</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#mobileMenu" aria-controls="mobileMenu" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="mobileMenu">
            <ul class="navbar-nav ml-auto mt-2 mt-lg-0">
                <li class="nav-item active">
                    <a class="nav-link" href="{{ path('homepage') }}">Accueil</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">Connexion</a>
                </li>
            </ul>
        </div>
    </nav>
</header>

Puis on l'inclus dans le template base.html.twig:

{# templates/base.html.twig #}
<!doctype html>
<html lang="fr">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    {% block stylesheets %}
        {# Bootstrap CSS #}
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    {% endblock %}

    <title>{% block title %}Symfony Ticketing App{% endblock %}</title>
</head>

<body>
    {% include 'includes/_header.html.twig' %}

    {% block content %}
        {# Le contenu de chaque page sera ici #}
    {% endblock %}

    {% include 'includes/_footer.html.twig' %}

    {% block scripts %}
        {# jQuery first, then Popper.js, then Bootstrap JS #}
        <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
    {% endblock %}
</body>

</html>

Nous utilisons cette fois-ci le mot clé include puis nous indiquons le chemin vers le fichier à inclure.

Le template est un concept super large et je te conseille de faire un tour sur la documentation et aussi sur Twig.

J'ai un peu modifier les fichiers core/index.html.twig et event/show.html.twig pour y mettre un peu de contenu, le tout se trouve sur le dépôt Github.

https://imgur.com/k3sZ2g9.jpg

Et l'affichage d'un événement:

https://imgur.com/MstkZGD.jpg

Maintenant que nous avons nos vues, nous allons nous limiter là. La prochaine fois nous allons parler de doctrine et des entités. A très bientôt.


Partage ce tutoriel


Merci à

Mamadou Aliou Diallo

Mamadou Aliou Diallo

@alioukahere

Développeur web fullstack avec une passion pour l’entrepreneuriat et les nouvelles technologies. Fondateur de Kaherecode.

Continue de lire

Discussion

Tu dois être connecté pour participer à la discussion. Me connecter.

Soidridine Ibrahima
@soidridine1 il y a 22 jours

@alioukahere ce tutoriel m'a fait pensé à mon Sénégal. J'ai commencé mes études la bas à l'école ISI avant de venir en France.

Tu dois être connecté pour participer à la discussion. Me connecter.
Mamadou Aliou Diallo
@alioukahere auteur il y a 22 jours

Ah ouais! J'ai aussi fait ISI, 2 années, c'est top ça. J'espère que tu as aimer le tutoriel.

Tu dois être connecté pour participer à la discussion. Me connecter.
Soidridine Ibrahima
@soidridine1 il y a 22 jours

Oui j’ai fais 2 ans à ISI j’habitais au parcelles aceni U15. Oui j’ai aimé le tutoriel mais à vrai dire je ne suis pas vraiment novice sur Symfony j’ai déjà développé des sites web mais ce que je veux vraiment voir c’est la suite du tutoriel. Quant il faut gérer les utilisateurs, le modérateur et l’admin. Les inscriptions aux événements ect….. J’ai hâte de voir ça. On a fait la Mr école ISI alors.

Tu dois être connecté pour participer à la discussion. Me connecter.
Mamadou Aliou Diallo
@alioukahere auteur il y a 22 jours

Yeah top, la suite viendra. Je vais essayer de m'en tenir à un tutoriel par semaine, chaque samedi.

Tu dois être connecté pour participer à la discussion. Me connecter.
Soidridine Ibrahima
@soidridine1 il y a 22 jours

Ça serait top si tu peux faire comme ça. Je tiens vraiment à ce tutoriel. Je suppose que ISI a changé ça fait très longtemps que j’ai quitté le Sénégal.

Tu dois être connecté pour participer à la discussion. Me connecter.
Mamadou Aliou Diallo
@alioukahere auteur il y a 22 jours

Yeah je ferais le max. Oui ISI évolue à sa manière, je ne suis pas de près mais ça évolue pas mal d'après ce que j'entends.

Tu dois être connecté pour participer à la discussion. Me connecter.
Soidridine Ibrahima
@soidridine1 il y a 22 jours

D’acc je vais t’écrire en privé sur LinkedIn.

Tu dois être connecté pour participer à la discussion. Me connecter.
Mamadou Aliou Diallo
@alioukahere auteur il y a 22 jours

Ça marche, Merci!

Tu dois être connecté pour participer à la discussion. Me connecter.