• 30 heures
  • Facile

Ce cours est visible gratuitement en ligne.

Ce cours est en vidéo.

Ce cours existe en livre papier.

Ce cours existe en eBook.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Le moteur de templates Twig

Connectez-vous ou inscrivez-vous pour bénéficier de toutes les fonctionnalités de ce cours !

 

Les templates, ou vues, sont très intéressants. Nous l'avons déjà vu, leur objectif est de séparer le code PHP du code HTML. Ainsi, lorsque vous faites du PHP, vous n'avez pas 100 balises HTML qui gênent la lecture de votre code PHP. De même, lorsque votre designer fait du HTML, il n'a pas à subir votre code barbare PHP auquel il ne comprend rien.

Intéressé ? Lisez la suite. ;)

Les templates Twig

Intérêt

Les templates vont nous permettre de séparer le code PHP du code HTML/XML/Text, etc. Seulement, pour faire du HTML de présentation, on a toujours besoin d'un peu de code dynamique : faire une boucle pour afficher toutes les annonces de notre plateforme, créer des conditions pour afficher un menu différent pour les utilisateurs authentifiés ou non, etc. Pour faciliter ce code dynamique dans les templates, le moteur de templates Twig offre son pseudo-langage à lui. Ce n'est pas du PHP, mais c'est plus adapté et voici pourquoi :

  • La syntaxe est plus concise et plus claire. Rappelez-vous, pour afficher une variable,{{ mavar }}suffit, alors qu'en PHP il faudrait faire<?php echo $mavar; ?>.

  • Il y a quelques fonctionnalités en plus, comme l'héritage de templates (nous le verrons). Cela serait bien entendu possible en PHP, mais il faudrait coder soi-même le système et cela ne serait pas aussi esthétique.

  • Il sécurise vos variables automatiquement : plus besoin de se soucier dehtmlentities(),addslashes()ou que sais-je encore.

Des pages web, mais aussi des e-mails et autres

En effet, pourquoi se limiter à nos pages HTML ? Les templates peuvent (et doivent) être utilisés partout. Quand on enverra des e-mails, leurs contenus seront placés dans un template. Il existe bien sûr un moyen de récupérer le contenu d'un template sans l'afficher immédiatement. Ainsi, en récupérant le contenu du template dans une variable quelconque, on pourra le passer à la fonction mail de notre choix.

Mais il en va de même pour un flux RSS par exemple ! Si l'on sait afficher une liste des news de notre site en HTML grâce au templateliste_news.html.twig, alors on saura afficher un fichier RSS en gardant le même contrôleur, mais en utilisant le templateliste_news.rss.twigà la place.

En pratique

On a déjà créé un template, mais un rappel ne fait pas de mal. Depuis le contrôleur, voici la syntaxe pour retourner une réponse HTTP toute faite, dont le contenu est celui d'un certain template :

<?php
// Depuis un contrôleur

return $this->render('OCPlatformBundle:Advert:index.html.twig', array(
  'var1' => $var1,
  'var2' => $var2
));

Et voici comment, au milieu d'un contrôleur, récupérer le contenu d'un template en texte :

<?php
// Depuis un contrôleur

$contenu = $this->renderView('OCPlatformBundle:Advert:email.txt.twig', array(
  'var1' => $var1,
  'var2' => $var2
));

// Puis on envoie l'e-mail, par exemple :
mail('moi@openclassrooms.com', 'Inscription OK', $contenu);

Et le templateOCPlatformBundle:Advert:email.txt.twig contiendrait par exemple :

{# src/OC/PlatformBundle/Resources/views/Advert/email.txt.twig #}

Bonjour {{ pseudo }},

Toute l'équipe du site se joint à moi pour vous souhaiter
la bienvenue sur notre site !

Revenez nous voir souvent !

À savoir

Première chose à savoir sur Twig : vous pouvez afficher des variables et pouvez exécuter des expressions. Ce n'est pas la même chose :

  • {{ … }}affiche quelque chose ;

  • {% … %}fait quelque chose ;

  • {# … #}n'affiche rien et ne fait rien : c'est la syntaxe pour les commentaires, qui peuvent être sur plusieurs lignes.

L'objectif de la suite de ce chapitre est donc :

  • D'abord, vous donner les outils pour afficher des variables : variables simples, tableaux, objets, appliquer des filtres, etc. ;

  • Ensuite, vous donner les outils pour construire un vrai code dynamique : faire des boucles, des conditions, etc. ;

  • Enfin, vous donner les outils pour organiser vos templates grâce à l'héritage et à l'inclusion de templates. Ainsi vous aurez un template maître qui contiendra votre design (avec les balises<html>,<head>, etc.) et vos autres templates ne contiendront que le contenu de la page (liste des news, etc.).

Afficher des variables

Syntaxe de base pour afficher des variables

Afficher une variable se fait avec les doubles accolades «{{ … }}». Voici quelques exemples.

Description

Exemple Twig

Équivalent PHP

Afficher une variable

Pseudo : {{ pseudo }}

Pseudo : <?php echo $pseudo; ?>

Afficher l'index d'un tableau

Identifiant : {{ user['id'] }}

Identifiant : <?php echo $user['id']; ?>

Afficher l'attribut d'un objet, dont le getter respecte la convention$objet->getAttribut()

Identifiant : {{ user.id }}

Identifiant : <?php echo $user->getId(); ?>

Afficher une variable en lui appliquant un filtre. Ici, « upper » met tout en majuscules :

Pseudo en majuscules : {{ pseudo|upper }}

Pseudo en lettre majuscules : <?php echo strtoupper($pseudo); ?>

Afficher une variable en combinant les filtres.
« striptags » supprime les balises HTML.
« title » met la première lettre de chaque mot en majuscule.
Notez l'ordre d'application des filtres, icistriptagsest appliqué, puistitle.

Message : {{ news.texte|striptags|title }}

Message : <?php echo ucwords(strip_tags($news->getTexte())); ?>

Utiliser un filtre avec des arguments.
Attention, il faut quedatesoit un objet de typeDatetimeici.

Date : {{ date|date('d/m/Y') }}

Date : <?php echo $date->format('d/m/Y'); ?>

Concaténer

Identité : {{ nom ~ " " ~ prenom }}

Identité : <?php echo $nom.' '.$prenom; ?>

Précisions sur la syntaxe{{ objet.attribut }}

Le fonctionnement de la syntaxe{{ objet.attribut }}est un peu plus complexe qu'elle n'en a l'air. Elle ne fait pas seulementobjet->getAttribut. En réalité, voici ce qu'elle fait exactement :

  • Elle vérifie siobjetest un tableau, et siattributest un index valide. Si c'est le cas, elle afficheobjet['attribut'].

  • Sinon, et siobjetest un objet, elle vérifie siattributest un attribut valide (public donc). Si c'est le cas, elle afficheobjet->attribut.

  • Sinon, et siobjetest un objet, elle vérifie siattribut()est une méthode valide (publique donc). Si c'est le cas, elle afficheobjet->attribut().

  • Sinon, et siobjetest un objet, elle vérifie sigetAttribut()est une méthode valide. Si c'est le cas, elle afficheobjet->getAttribut().

  • Sinon, et siobjetest un objet, elle vérifie siisAttribut()est une méthode valide. Si c'est le cas, elle afficheobjet->isAttribut().

  • Sinon, elle n'affiche rien et retournenull.

Les filtres utiles

Il y a quelques filtres disponibles nativement avec Twig, en voici quelques-uns :

Filtre

Description

Exemple Twig

Upper

Met toutes les lettres en majuscules.

{{ var|upper }}

Striptags

Supprime toutes les balises XML.

{{ var|striptags }}

Date

Formate la date selon le format donné en argument. La variable en entrée doit être une instance deDatetime.

{{ date|date('d/m/Y') }}
Date d'aujourd'hui : {{ "now"|date('d/m/Y') }}

Format

Insère des variables dans un texte, équivalent àprintf.

{{ "Il y a %s pommes et %s poires"|format(153, nb_poires) }}

Length

Retourne le nombre d'éléments du tableau, ou le nombre de caractères d'une chaîne.

Longueur de la variable : {{ texte|length }}
Nombre d'éléments du tableau : {{ tableau|length }}

Nous pourrons également créer nos propres filtres ! On le verra plus loin dans ce cours.

Twig et la sécurité

Dans tous les exemples précédents, vos variables ont déjà été protégées par Twig ! Twig applique par défaut un filtre sur toutes les variables que vous affichez, afin de les protéger de balises HTML malencontreuses. Ainsi, si le pseudo d'un de vos membres contient un «<» par exemple, lorsque vous faites{{ pseudo }}celui-ci est échappé, et le texte généré est en réalité «mon&lt;pseudo» au lieu de «mon<pseudo», ce qui poserait problème dans votre structure HTML. Très pratique ! Et donc à savoir : inutile de protéger vos variables en amont, Twig s'occupe de tout en fin de chaîne !

Et dans le cas où vous voulez afficher volontairement une variable qui contient du HTML (JavaScript, etc.), et que vous ne voulez pas que Twig l'échappe, il vous faut utiliser le filtrerawcomme ceci :{{ ma_variable_html|raw }}. Avec ce filtre, Twig désactivera localement la protection HTML, et affichera la variable en brut, quel que soit ce qu'elle contient.

Les variables globales

Symfony2 enregistre par défaut une variable globale{{ app }} dans Twig pour nous faciliter la vie. Voici la liste de ses attributs, qui sont donc disponibles dans tous vos templates :

Variable

Description

{{ app.request }}

La requête « request » qu'on a vu au chapitre précédent sur les contrôleurs.

{{ app.session }}

Le service « session » qu'on a vu également au chapitre précédent.

{{ app.environment }}

L'environnement courant : « dev », « prod », et ceux que vous avez définis.

{{ app.debug }}

Truesi le mode debug est activé,Falsesinon.

{{ app.user }}

L'utilisateur courant, que nous verrons également plus loin dans ce cours.

Bien entendu, on peut enregistrer nos propres variables globales, pour qu'elles soient accessibles depuis toutes nos vues, au lieu de les injecter à chaque fois depuis le contrôleur. Pour cela, il faut éditer le fichier de configuration de l'application, comme suit :

# app/config/config.yml

# …

twig:
    # …
    globals:
        webmaster: moi-même

Ainsi, la variable{{ webmaster }}sera injectée dans toutes vos vues, et donc utilisable comme ceci :

<footer>Responsable du site : {{ webmaster }}.</footer>

Je profite de cet exemple pour vous faire passer un petit message. Pour ce genre de valeurs paramétrables, la bonne pratique est de les définir non pas directement dans le fichier de configurationconfig.yml, mais dans le fichier des paramètres, à savoirparameters.yml. Attention, je parle bien de la valeur du paramètre, non de la configuration. Voyez par vous-mêmes.

Valeur du paramètre :

# app/config/parameters.yml

parameters:
    # …
    app_webmaster: moi-même

Configuration (ici, injection dans toutes les vues) qui utilise le paramètre :

# app/config/config.yml

twig:
    globals:
        webmaster: %app_webmaster%

On a ainsi séparé la valeur du paramètre, stockée dans un fichier simple, et l'utilisation de ce paramètre, perdue dans le fichier de configuration.

Structures de contrôle et expressions

Les structures de contrôle

Nous avons vu comment afficher quelque chose, maintenant nous allons faire des choses, avec la syntaxe{% … %}.

Condition :{% if %}

Exemple Twig :

{% if membre.age < 12 %}
  Il faut avoir au moins 12 ans pour ce film.
{% elseif membre.age < 18 %}
  OK bon film.
{% else %}
  Un peu vieux pour voir ce film non ?
{% endif %}

Équivalent PHP :

<?php if($membre->getAge() < 12) { ?>
  Il faut avoir au moins 12 ans pour ce film.
<?php } elseif($membre->getAge() < 18) { ?>
  OK bon film.
<?php } else { ?>
  Un peux vieux pour voir ce film non ?
<?php } ?>
Boucle :{% for %}

Exemple Twig :

<ul>
  {% for membre in liste_membres %}
    <li>{{ membre.pseudo }}</li>
  {% else %}
    <li>Pas d'utilisateur trouvé.</li>
  {% endfor %}
</ul>

Et pour avoir accès aux clés du tableau :

<select>
  {% for valeur, option in liste_options %}
    <option value="{{ valeur }}">{{ option }}</option>
  {% endfor %}
</select>

Équivalent PHP :

<ul>
<?php if(count($liste_membres) > 0) {
  foreach($liste_membres as $membre) {
    echo '<li>'.$membre->getPseudo().'</li>';
  }
} else { ?>
  <li>Pas d'utilisateur trouvé.</li>
<?php } ?>
</ul>

Avec les clés :

<?php
foreach($liste_options as $valeur => $option) {
  // …
}
Définition :{% set %}

Exemple Twig :

{% set foo = 'bar' %}

Équivalent PHP :

<?php $foo = 'bar'; ?>

Une petite information sur la structure{% for %}, celle-ci définit une variable{{ loop }}au sein de la boucle, qui contient les attributs suivants :

Variable

Description

{{ loop.index }}

Le numéro de l'itération courante (en commençant par 1).

{{ loop.index0 }}

Le numéro de l'itération courante (en commençant par 0).

{{ loop.revindex }}

Le nombre d'itérations restantes avant la fin de la boucle (en finissant par 1).

{{ loop.revindex0 }}

Le nombre d'itérations restantes avant la fin de la boucle (en finissant par 0).

{{ loop.first }}

truesi c'est la première itération,falsesinon.

{{ loop.last }}

truesi c'est la dernière itération,falsesinon.

{{ loop.length }}

Le nombre total d'itérations dans la boucle.

Les tests utiles

Defined

Pour vérifier si une variable existe.

Exemple Twig :

{% if var is defined %} … {% endif %}

Équivalent PHP :

<?php if(isset($var)) { }
Even/Odd

Pour tester si un nombre est pair / impair.

Exemple Twig :

{% for valeur in liste %}
  <span class="{% if loop.index is even %}pair{% else %}
    impair{% endif %}">
    {{ valeur }}
  </span>
{% endfor %}

Équivalent PHP :

<?php
$i = 0;
foreach($liste as $valeur) {
  echo '<span class="';
  echo $i % 2 ? 'impair' : 'pair';
  echo '">'.$valeur.'</span>';
  $i++;
}

Hériter et inclure des templates

L'héritage de template

Je vous ai fait un teaser précédemment : l'héritage de templates va nous permettre de résoudre la problématique : « J'ai un seul design et n'ai pas l'envie de le répéter sur chacun de mes templates ». C'est un peu comme ce que vous devez faire aujourd'hui avec lesinclude(), mais en mieux !

Le principe

Le principe est simple : vous avez un template père qui contient le design de votre site ainsi que quelques trous (appelés « blocks » en anglais, que nous nommerons « blocs » en français) et des templates fils qui vont remplir ces blocs. Les fils vont donc venir hériter du père en remplaçant certains éléments par leur propre contenu.

L'avantage est que les templates fils peuvent modifier plusieurs blocs du template père. Avec la technique desinclude(), un template inclus ne pourra pas modifier le template père dans un autre endroit que là où il est inclus !

Les blocs classiques sont le centre de la page et le titre. Mais en fait, c'est à vous de les définir ; vous en ajouterez donc autant que vous voudrez.

La pratique

Voici à quoi peut ressembler un template père (appelé plus communément layout). Mettons-le danssrc/OC/PlatformBundle/Resources/views/layout.html.twig:

{# src/OC/PlatformBundle/Resources/views/layout.html.twig #}

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="utf-8">
    <title>{% block title %}OC Plateforme{% endblock %}</title>
  </head>
  <body>

    {% block body %}
    {% endblock %}

  </body>
</html>

Et voici un de nos templates fils. Mettons-le danssrc/OC/PlatformBundle/Resources/views/Advert/index.html.twig:

{# src/OC/PlatformBundle/Resources/views/Advert/index.html.twig #}

{% extends "OCPlatformBundle::layout.html.twig" %}

{% block title %}{{ parent() }} - Index{% endblock %}

{% block body %}
  Notre plateforme est un peu vide pour le moment, mais cela viendra !
{% endblock %}

Qu'est-ce que l'on vient de faire ?

Pour bien comprendre tous les concepts utilisés dans cet exemple très simple, détaillons un peu.

Le nom du template père

On a placé ce template dansviews/layout.html.twiget non dansviews/qqch/layout.html.twig. C'est tout à fait possible ! En fait, il est inutile de mettre dans un sous-répertoire les templates qui ne concernent pas un contrôleur particulier et qui peuvent être réutilisés par plusieurs contrôleurs. Attention à la notation pour accéder à ce template : du coup, ce n'est plusOCPlatformBundle:MonController:layout.html.twig, maisOCPlatformBundle::layout.html.twig. C'est assez intuitif, en fait : on enlève juste la partie qui correspond au répertoireMonController. C'est ce que l'on a fait à la première ligne du template fils.

La balise{% block %}côté père

Pour définir un « trou » (dit bloc) dans le template père, nous avons utilisé la balise{% block %}. Un bloc doit avoir un nom afin que le template fils puisse modifier tel ou tel bloc de façon nominative. La base, c'est juste de faire{% block nom_du_block %}{% endblock %}et c'est ce que nous avons fait pour le body. Mais vous pouvez insérer un texte par défaut dans les blocs, comme on l'a fait pour le titre. C'est utile pour deux cas de figure :

  • Lorsque le template fils ne redéfinit pas ce bloc. Plutôt que de n'avoir rien d'écrit, vous aurez cette valeur par défaut.

  • Lorsque les templates fils veulent réutiliser une valeur commune. Par exemple, si vous souhaitez que le titre de toutes les pages de votre site commence par « OC Plateforme », alors depuis les templates fils, vous pouvez utiliser{{ parent() }}qui permet d'utiliser le contenu par défaut du bloc côté père. Regardez, nous l'avons fait pour le titre dans le template fils.

La balise{% block %}côté fils

Elle se définit exactement comme dans le template père, sauf que cette fois-ci on y met notre contenu. Mais étant donné que les blocs se définissent et se remplissent de la même façon, vous avez pu deviner qu'on peut hériter en cascade ! En effet, si l'on crée un troisième template petit-fils qui hérite de fils, on pourra faire beaucoup de choses.

Le modèle « triple héritage »

Pour bien organiser ses templates, une bonne pratique est sortie du lot. Il s'agit de faire de l'héritage de templates sur trois niveaux, chacun des niveaux remplissant un rôle particulier. Les trois templates sont les suivants :

  • Layout général : c'est le design de votre site, indépendamment de vos bundles. Il contient le header, le footer, etc. La structure de votre site donc (c'est notre template père).

  • Layout du bundle : il hérite du layout général et contient les parties communes à toutes les pages d'un même bundle. Par exemple, pour notre plateforme d'annonce, on pourrait afficher un menu particulier, rajouter « Annonces » dans le titre, etc.

  • Template de page : il hérite du layout du bundle et contient le contenu central de votre page.

Nous verrons un exemple de ce triple héritage juste après.

Question : puisque le layout général ne dépend pas d'un bundle en particulier, où le mettre ?

Dans votre répertoire/app! En effet, dans ce répertoire, vous pouvez toujours avoir des fichiers qui écrasent ceux des bundles ou bien des fichiers communs aux bundles. Le layout général de votre site fait partie de ces ressources communes. Son répertoire exact doit êtreapp/Resources/views/layout.html.twig.

Et pour l'appeler depuis vos templates, la syntaxe est la suivante : «::layout.html.twig». Encore une fois, c'est très intuitif : après avoir enlevé le nom du contrôleur tout à l'heure, on enlève juste cette fois-ci le nom du bundle.

Afin de bien vous représenter l'architecture adoptée, je vous propose un petit schéma à la figure suivante. Il vaut ce qu'il vaut, mais vous permet de bien comprendre ce qu'on fait.

Héritage de templates sur trois niveaux
Héritage de templates sur trois niveaux

L'inclusion de templates

La théorie : quand faire de l'inclusion ?

Hériter, c'est bien, mais inclure, ce n'est pas mal non plus. Prenons un exemple pour bien faire la différence.

Le formulaire pour ajouter une annonce est le même que celui pour… modifier une annonce. On ne va pas faire du copier-coller de code, cela serait assez moche, et puis nous sommes fainéants. C'est ici que l'inclusion de templates intervient. On a nos deux templatesOCPlatformBundle:Advert:add.html.twigetOCPlatformBundle:Advert:edit.html.twigqui héritent chacun deOCPlatformBundle::layout.html.twig.

L'affichage exact de ces deux templates diffère un peu, mais chacun d'eux inclutOCPlatformBundle:Advert:form.html.twigà l'endroit exact pour afficher le formulaire.

On voit bien qu'on ne peut pas faire d'héritage sur le templateform.html.twig, car il faudrait le faire hériter une fois deadd.html.twig, une fois deedit.html.twig, etc. Comment savoir ? Et si un jour, nous souhaitons ne le faire hériter de rien du tout pour afficher le formulaire tout seul dans une popup par exemple ? Bref, c'est bien une inclusion qu'il nous faut ici.

La pratique : comment le faire ?

Comme toujours avec Twig, cela se fait très facilement. Il faut utiliser la fonction {{ include() }}, comme ceci :

{{ include("OCPlatformBundle:Advert:form.html.twig") }}

Ce code inclura le contenu du template à l'endroit de la balise. Une sorte de copier-coller automatique, en fait ! Voici un exemple avec la vueadd.html.twig:

{# src/OC/PlatformBundle/Resources/views/Advert/add.html.twig #}

{% extends "OCPlatformBundle::layout.html.twig" %}

{% block body %}

  <h2>Ajouter une annonce</h2>

  {{ include("OCPlatformBundle:Advert:form.html.twig") }}

  <p>
    Attention : cette annonce sera ajoutée directement
    sur la page d'accueil après validation du formulaire.
  </p>

{% endblock %}

Et voici le code du template inclus (ici, le formulaire) :

{# src/OC/PlatformBundle/Resources/views/Advert/form.html.twig #}

{# Cette vue n'hérite de personne, elle sera incluse par d'autres vues qui,
   elles, hériteront probablement du layout. Je dis « probablement » car,
   ici pour cette vue, on n'en sait rien et c'est une info qui ne nous concerne pas. #}

<h3>Formulaire d'annonce</h3>

{# On laisse vide la vue pour l'instant, on la comblera plus tard
   lorsqu'on saura afficher un formulaire. #}
<div class="well">
  Ici se trouvera le formulaire.
</div>

L'inclusion de contrôleurs

La théorie : quand inclure des contrôleurs ?

Voici un dernier point à savoir absolument avec Twig, un des points les plus puissants dans son utilisation avec Symfony2. On vient de voir comment inclure des templates : ceux-ci profitent des variables du template qui fait l'inclusion, très bien.

Seulement dans bien des cas, depuis le template qui fait l'inclusion, vous voudrez inclure un autre template, mais vous n'avez pas les variables nécessaires pour lui. Restons sur l'exemple de notre plateforme d'annonces, dans le schéma précédent je vous ai mis un bloc rouge : considérons que dans cette partie du menu, accessible sur toutes les pages même hors de la liste des annonces, on veut afficher les 3 dernières annonces.

C'est donc depuis le layout général qu'on va inclure non pas un template — nous n'aurions pas les variables à lui donner —, mais un contrôleur. Le contrôleur va créer les variables dont il a besoin, et les donner à son template, pour ensuite être inclus là où on le veut !

La pratique : comment le faire ?

Au risque de me répéter : cela se fait très simplement !

Du côté du template qui fait l'inclusion, à la place de la fonction {{ include() }}, il faut utiliser la fonction{{ render() }}, comme ceci :

{{ render(controller("OCPlatformBundle:Advert:menu")) }}

Ici,OCPlatformBundle:Advert:menun'est pas un template mais une action de contrôleur, c'est la syntaxe qu'on utilise dans les routes, vous l'aurez reconnue.

Voici par exemple ce qu'on mettrait dans le layout :

{# src/OC/PlatformBundle/Resources/views/layout.html.twig #}

<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="utf-8">
    <title>{% block title %}OC Plateforme{% endblock %}</title>
  </head>
  <body>

    <div id="menu">
      {{ render(controller("OCPlatformBundle:Advert:menu")) }}
    </div>

    {% block body %}
    {% endblock %}

  </body>
</html>

Et du côté du contrôleur, on ajoute la méthodemenuAction()  très classique, qui retourne une réponse avec le templatemenu comme contenu :

<?php

// src/OC/PlatformBundle/Controller/AdvertController.php

namespace OC\PlatformBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class AdvertController extends Controller
{
  // ...

  public function menuAction()
  {
    // On fixe en dur une liste ici, bien entendu par la suite
    // on la récupérera depuis la BDD !
    $listAdverts = array(
      array('id' => 2, 'title' => 'Recherche développeur Symfony2'),
      array('id' => 5, 'title' => 'Mission de webmaster'),
      array('id' => 9, 'title' => 'Offre de stage webdesigner')
    );

    return $this->render('OCPlatformBundle:Advert:menu.html.twig', array(
      // Tout l'intérêt est ici : le contrôleur passe
      // les variables nécessaires au template !
      'listAdverts' => $listAdverts
    ));
  }
}

Et enfin, un exemple de ce que pourrait être le templatemenu.html.twig:

{# src/OC/PlatformBundle/Resources/views/Advert/menu.html.twig #}

{# Ce template n'hérite de personne,
   tout comme le template inclus avec {{ include() }}. #}

<ul class="nav nav-pills nav-stacked">
  {% for advert in listAdverts %}
    <li>
      <a href="{{ path('oc_platform_view', {'id': advert.id}) }}">
        {{ advert.title }}
      </a>
    </li>
  {% endfor %}
</ul>

Application : les templates de notre plateforme

Revenons à notre plateforme. Faites en sorte d'avoir sous la main le contrôleur que l'on a réalisé au chapitre précédent. Le but ici est de créer tous les templates que l'on a utilisés depuis le contrôleur, ou du moins son squelette. Étant donné que l'on n'a pas encore accès à la base de données, on va faire avec des variables vides : cela va se remplir par la suite, mais le fait d'employer des variables vides va nous permettre dès maintenant de construire le template.

Pour encadrer tout ça, nous allons utiliser le modèle d'héritage sur trois niveaux : layout général, layout du bundle et template.

Layout général

La théorie

Comme évoqué précédemment, le layout est la structure HTML de notre site avec des blocs aux endroits stratégiques pour permettre aux templates qui hériteront de ce dernier de personnaliser la page. On va ici créer une structure simple ; je vous laisse la personnaliser si besoin est. Pour les blocs, pareil pour l'instant, on fait simple : un bloc pour le body et un bloc pour le titre.

La pratique

Commençons par faire le layout général de l'application, la vue située dans le répertoire/app. Voici le code exemple que je vous propose :

{# app/Resources/views/layout.html.twig #}

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>{% block title %}OC Plateforme{% endblock %}</title>

  {% block stylesheets %}
    {# On charge le CSS de bootstrap depuis le site directement #}
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
  {% endblock %}
</head>

<body>
  <div class="container">
    <div id="header" class="jumbotron">
      <h1>Ma plateforme d'annonces</h1>
      <p>
        Ce projet est propulsé par Symfony2,
        et construit grâce au MOOC OpenClassrooms et SensioLabs.
      </p>
      <p>
        <a class="btn btn-primary btn-lg" href="http://fr.openclassrooms.com/informatique/cours/developpez-votre-site-web-avec-le-framework-symfony2">
          Participer au MOOC »
        </a>
      </p>
    </div>

    <div class="row">
      <div id="menu" class="col-md-3">
        <h3>Les annonces</h3>
        <ul class="nav nav-pills nav-stacked">
          <li><a href="{{ path('oc_platform_home') }}">Accueil</a></li>
          <li><a href="{{ path('oc_platform_add') }}">Ajouter une annonce</a></li>
        </ul>

        <h4>Dernières annonces</h4>
        {{ render(controller("OCPlatformBundle:Advert:menu", {'limit': 3})) }}
      </div>
      <div id="content" class="col-md-9">
        {% block body %}
        {% endblock %}
      </div>
    </div>

    <hr>

    <footer>
      <p>The sky's the limit © {{ 'now'|date('Y') }} and beyond.</p>
    </footer>
  </div>

  {% block javascripts %}
    {# Ajoutez ces lignes JavaScript si vous comptez vous servir des fonctionnalités du bootstrap Twitter #}
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
  {% endblock %}

</body>
</html>

Voici les lignes qui contiennent un peu de Twig :

  • Ligne 9 : création du bloc « title » avec « OC Plateforme » comme contenu par défaut ;

  • Lignes 36 et 37 : utilisation de la fonction{{ path }}pour faire des liens vers d'autres routes ;

  • Ligne 41 : inclusion de la méthodemenudu contrôleur Advert du bundle OCPlatformBundle, avec l'argumentnombredéfini à 3 ;

  • Lignes 44 et 45 : création du bloc « body » sans contenu par défaut.

Et voilà, nous avons notre layout général ! Pour pouvoir tester nos pages, il faut maintenant s'attaquer au layout du bundle.

Layout du bundle

La théorie

Comme on l'a dit, ce template va hériter du layout général, ajouter la petite touche personnelle au bundle Advert, puis se faire hériter à son tour par les templates finaux. En fait, il ne contient pas grand-chose. Laissez courir votre imagination, mais, moi, je ne vais rajouter qu'une balise<h1>, vous voyez ainsi le mécanisme et pouvez personnaliser à votre sauce.

La seule chose à laquelle il faut faire attention, c'est au niveau du nom des blocs que ce template crée pour ceux qui vont l'hériter. Une bonne pratique consiste à préfixer le nom des blocs par le nom du bundle courant. Regardez le code et vous comprendrez.

La pratique

Voici ce que j'ai mis pour le layout du bundle :

{# src/OC/PlatformBundle/Resources/views/layout.html.twig #}

{% extends "::layout.html.twig" %}

{% block title %}
  Annonces - {{ parent() }}
{% endblock %}

{% block body %}

  {# On définit un sous-titre commun à toutes les pages du bundle, par exemple #}
  <h1>Annonces</h1>

  <hr>

  {# On définit un nouveau bloc, que les vues du bundle pourront remplir #}
  {% block ocplatform_body %}
  {% endblock %}

{% endblock %}

On a ajouté un<h1>dans le blocbody, puis créé un nouveau bloc qui sera personnalisé par les templates finaux. On a préfixé le nom du nouveau bloc pour lebodyafin d'avoir un nom unique pour notre bundle.

Les templates finaux

Advert/index.html.twig

C'est le template de la page d'accueil. On va faire notre première boucle sur la variable{{ listAdverts }}. Cette variable n'existe pas encore, on va modifier le contrôleur juste après.

{# src/OC/PlatformBundle/Resources/views/Advert/index.html.twig #}

{% extends "OCPlatformBundle::layout.html.twig" %}

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

{% block ocplatform_body %}

  <h2>Liste des annonces</h2>

  <ul>
    {% for advert in listAdverts %}
      <li>
        <a href="{{ path('oc_platform_view', {'id': advert.id}) }}">
          {{ advert.title }}
        </a>
        par {{ advert.author }},
        le {{ advert.date|date('d/m/Y') }}
      </li>
    {% else %}
      <li>Pas (encore !) d'annonces</li>
    {% endfor %}
  </ul>

{% endblock %}

Pas grand-chose à dire, on a juste utilisé les variables et expressions expliquées dans ce chapitre.

Afin que cette page fonctionne, il nous faut modifier l'actionindexAction()du contrôleur pour passer une variable{{ listAdverts }}à cette vue. Pour l'instant, voici juste de quoi se débarrasser de l'erreur :

<?php
// src/OC/PlatformBundle/Controller/AdvertController.php

// Dans l'action indexAction() :
return $this->render('OCPlatformBundle:Advert:index.html.twig', array(
  'listAdverts' => array()
));

Si vous n'aviez pas rajouté l'actionmenudu contrôleur tout à l'heure, voici comment le faire, et aussi comment l'adapter à l'argument qu'on lui a passé cette fois-ci :

<?php
// src/OC/PlatformBundle/Controller/AdvertController.php

  public function menuAction($limit)
  {
    // On fixe en dur une liste ici, bien entendu par la suite
    // on la récupérera depuis la BDD !
    $listAdverts = array(
      array('id' => 2, 'title' => 'Recherche développeur Symfony2'),
      array('id' => 5, 'title' => 'Mission de webmaster'),
      array('id' => 9, 'title' => 'Offre de stage webdesigner')
    );

    return $this->render('OCPlatformBundle:Advert:menu.html.twig', array(
      // Tout l'intérêt est ici : le contrôleur passe
      // les variables nécessaires au template !
      'listAdverts' => $listAdverts
    ));
  }

Avec sa vue associée :

{# src/OC/PlatformBundle/Resources/views/Advert/menu.html.twig #}

<ul class="nav nav-pills nav-stacked">
  {% for advert in listAdverts %}
    <li>
      <a href="{{ path('oc_platform_view', {'id': advert.id}) }}">
        {{ advert.title }}
      </a>
    </li>
  {% endfor %}
</ul>

Vous voulez voir des annonces au lieu du message pas très drôle comme quoi il n'y a pas encore d'annonce ? Voici un tableau d'annonces à ajouter temporairement dans la méthodeindexAction(), que vous pouvez passer en paramètre à la méthoderender(). C'est un tableau pour l'exemple, par la suite il faudra bien sûr récupérer les annonces depuis la base de données !

<?php
// src/OC/PlatformBundle/Controller/AdvertController.php

// …

public function indexAction($page)
  {
    // ...

    // Notre liste d'annonce en dur
    $listAdverts = array(
      array(
        'title'   => 'Recherche développpeur Symfony2',
        'id'      => 1,
        'author'  => 'Alexandre',
        'content' => 'Nous recherchons un développeur Symfony2 débutant sur Lyon. Blabla…',
        'date'    => new \Datetime()),
      array(
        'title'   => 'Mission de webmaster',
        'id'      => 2,
        'author'  => 'Hugo',
        'content' => 'Nous recherchons un webmaster capable de maintenir notre site internet. Blabla…',
        'date'    => new \Datetime()),
      array(
        'title'   => 'Offre de stage webdesigner',
        'id'      => 3,
        'author'  => 'Mathieu',
        'content' => 'Nous proposons un poste pour webdesigner. Blabla…',
        'date'    => new \Datetime())
    );

    // Et modifiez le 2nd argument pour injecter notre liste
    return $this->render('OCPlatformBundle:Advert:index.html.twig', array(
      'listAdverts' => $listAdverts
    ));
  }

Rechargez la page, et profitez du résultat. ;) Si vous avez bien ajouté le CSS de Twitter, le résultat devrait ressembler à la figure suivante.

Le rendu de notre blog
Le rendu de notre site
Advert/view.html.twig

Il ressemble beaucoup àindex.html.twigsauf qu'on passe à la vue une variable{{ advert }}contenant une seule annonce, et non plus une liste d'annonces. Voici un code par exemple :

{# src/OC/PlatformBundle/Resources/view/Advert/view.html.twig #}

{% extends "OCPlatformBundle::layout.html.twig" %}

{% block title %}
  Lecture d'une annonce - {{ parent() }}
{% endblock %}

{% block ocplatform_body %}

  <h2>{{ advert.title }}</h2>
  <i>Par {{ advert.author }}, le {{ advert.date|date('d/m/Y') }}</i>

  <div class="well">
    {{ advert.content }}
  </div>

  <p>
    <a href="{{ path('oc_platform_home') }}" class="btn btn-default">
      <i class="glyphicon glyphicon-chevron-left"></i>
      Retour à la liste
    </a>
    <a href="{{ path('oc_platform_edit', {'id': advert.id}) }}" class="btn btn-default">
      <i class="glyphicon glyphicon-edit"></i>
      Modifier l'annonce
    </a>
    <a href="{{ path('oc_platform_delete', {'id': advert.id}) }}" class="btn btn-danger">
      <i class="glyphicon glyphicon-trash"></i>
      Supprimer l'annonce
    </a>
  </p>

{% endblock %}

Et l'adaptation du contrôleur bien évidemment :

<?php
// src/OC/PlatformBundle/Controller/AdvertController.php

// …

public function viewAction($id)
  {
    $advert = array(
      'title'   => 'Recherche développpeur Symfony2',
      'id'      => $id,
      'author'  => 'Alexandre',
      'content' => 'Nous recherchons un développeur Symfony2 débutant sur Lyon. Blabla…',
      'date'    => new \Datetime()
    );

    return $this->render('OCPlatformBundle:Advert:view.html.twig', array(
      'advert' => $advert
    ));
  }

La figure suivante représente le rendu de/platform/advert/1.

Visualisation d'un article
Visualisation d'une annonce
Advert/edit.html.twigetadd.html.twig

Ceux-ci contiennent une inclusion de template. En effet, rappelez-vous, j'avais pris l'exemple d'un formulaire utilisé pour l'ajout, mais également la modification. C'est notre cas ici, justement. Voici donc le fichieredit.html.twig:

{# src/OC/PlatformBundle/Resources/views/Advert/edit.html.twig #}

{% extends "OCPlatformBundle::layout.html.twig" %}

{% block title %}
  Modifier une annonce - {{ parent() }}
{% endblock %}

{% block ocplatform_body %}

  <h2>Modifier une annonce</h2>

  {{ include("OCPlatformBundle:Advert:form.html.twig") }}

  <p>
    Vous éditez une annonce déjà existante, merci de ne pas changer
    l'esprit générale de l'annonce déjà publiée.
  </p>

  <p>
    <a href="{{ path('oc_platform_view', {'id': advert.id}) }}" class="btn btn-default">
      <i class="glyphicon glyphicon-chevron-left"></i>
      Retour à l'annonce
    </a>
  </p>

{% endblock %}

Le templateadd.html.twiglui ressemble énormément, je vous laisse donc le faire.

Quant àform.html.twig, on ne sait pas encore le faire, car il demande des notions de formulaire, mais faisons déjà sa structure pour le moment (vous devriez déjà l'avoir) :

{# src/OC/PlatformBundle/Resources/views/Advert/form.html.twig #}

<h3>Formulaire d'annonce</h3>

{# On laisse vide la vue pour l'instant, on la comblera plus tard
   lorsqu'on saura afficher un formulaire. #}
<div class="well">
  Ici se trouvera le formulaire.
</div>

Une chose importante ici : dans ce template, il n'y a aucune notion de bloc, d'héritage, etc. Ce template est un électron libre : vous pouvez l'inclure depuis n'importe quel autre template.

Et, bien sûr, il faut adapter le contrôleur pour passer la variable$advert  :

<?php
// src/OC/PlatformBundle/Controller/AdvertController.php

  public function editAction($id, Request $request)
  {
    // ...
    
    $advert = array(
      'title'   => 'Recherche développpeur Symfony2',
      'id'      => $id,
      'author'  => 'Alexandre',
      'content' => 'Nous recherchons un développeur Symfony2 débutant sur Lyon. Blabla…',
      'date'    => new \Datetime()
    );

    return $this->render('OCPlatformBundle:Advert:edit.html.twig', array(
      'advert' => $advert
    ));
  }

Ainsi,/platform/edit/1nous donnera la figure suivante.

Modification d'un article
Modification d'une annonce

Pour conclure

Et voilà, nous avons généré presque tous nos templates. Bien sûr, ils sont encore un peu vides, car on ne sait pas utiliser les formulaires ni récupérer les annonces depuis la base de données. Mais vous savez maintenant les réaliser et c'était une étape importante ! Je vais vous laisser créer les templates manquants ou d'autres afin que vous vous fassiez la main. Bon code !

Cela termine ce chapitre : vous savez afficher avec mise en forme le contenu de votre site. Vous avez maintenant presque toutes les billes en main pour réaliser un site internet. Bon, OK, c'est vrai, il vous manque encore des concepts clés tels que les formulaires, la base de données, etc. Mais vous maîtrisez pleinement la base du framework Symfony2, et apprendre ces prochains concepts sera bien plus facile !

Pour plus d'informations concernant Twig et ses possibilités, n'hésitez pas à lire la documentation officielle.

En résumé

  • Un moteur de templates tel que Twig permet de bien séparer le code PHP du code HTML, dans le cadre de l'architecture MVC ;

  • La syntaxe{{ var }}affiche la variablevar;

  • La syntaxe{% if %} fait quelque chose, ici une condition ;

  • Twig offre un système d'héritage (via{% extends %}) et d'inclusion (via{{ include() }}et{{ render() }}) très intéressant pour bien organiser les templates ;

  • Le modèle triple héritage est très utilisé pour des projets avec Symfony2.

Exemple de certificat de réussite
Exemple de certificat de réussite