Mon premier « Hello World ! » avec Symfony2

L'objectif de ce chapitre est de créer de toutes pièces notre première page avec Symfony2 : une simple page blanche comprenant un « Hello World ! ». Nous allons donc créer tous les éléments indispensables pour concevoir une telle page.

Nous allons avoir une vue d'ensemble de tous les acteurs qui interviennent dans la création d'une page : routeur, contrôleur et template. Pour cela, tous les détails ne seront pas expliqués afin qu'on se concentre sur l'essentiel : la façon dont ils se coordonnent. Vous devrez attendre les prochains chapitres pour étudier un à un ces trois acteurs, patience donc !

Ne bloquez donc pas sur un point si vous ne comprenez pas tout, forcez-vous juste à comprendre l'ensemble. À la fin du chapitre, vous aurez une vision globale de la création d'une page et l'objectif sera atteint.

Bonne lecture !

Créons notre route

Nous travaillons dans notre bundle SdzBlogBundle, placez-vous donc dans son répertoire : src/Sdz/BlogBundle.

Pour créer une page, il faut d'abord définir l'URL à laquelle elle sera accessible. Pour cela, il faut créer la route de cette page.

Le routeur (ou router) ? Une route ?

Objectif

L'objectif du routeur est de dire à Symfony2 ce qu'il doit faire lorsque l'on appelle l'URL /hello-world (par exemple). Nous devons donc créer une route qui va dire : « Lorsque l'on est sur l'URL /hello-world, alors on appelle le contrôleur "Blog" qui va afficher un "Hello World !". » Regardez la figure suivante.

Fonctionnement du routeur

Comme je l'ai dit, nous ne toucherons ni au noyau, ni au routeur : nous nous occuperons juste des routes.

1. Créons notre fichier de routes

Les routes se définissent dans un simple fichier texte, que Symfony2 a déjà généré pour notre SdzBlogBundle. Usuellement, on nomme ce fichier Resources/config/routing.yml dans le répertoire du bundle. Ouvrez le fichier, et ajoutez cette route à la suite de celle qui existe déjà :

1
2
3
4
5
# src/Sdz/BlogBundle/Resources/config/routing.yml

HelloTheWorld:
    path:     /hello-world
    defaults: { _controller: SdzBlogBundle:Blog:index }

Vous venez de créer votre première route !

Contrairement à ce que vous pourriez penser, l'indentation se fait avec 4 espaces par niveau, pas avec des tabulations ! Je le précise parce qu'un jour vous ferez l'erreur (l'inévitable ne peut être évité), et vous me remercierez de vous avoir mis sur la voie. Et cela est valable pour tous vos fichiers .yml.

Attention également, il semble y avoir des erreurs lors des copier-coller depuis le tutoriel vers les fichiers .yml. Si vous rencontrez une obscure erreur, pensez à bien définir l'encodage du fichier en « UTF-8 sans BOM » et à supprimer les éventuels caractères non désirés. C'est un bug étrange qui provient du site, mais dont on ne connaît pas l'origine. L'esquive est de toujours recopier les exemples YAML que je vous donne, et de ne pas les copier-coller.

Si vous utilisez une version antérieure à Symfony 2.2, c'est « pattern » qu'il vous faut utiliser en lieu et place du « path » qu'on a utilisé ici. Cela ne change strictement rien au comportement, c'est juste le nom qui a changé.

Essayons de comprendre rapidement cette route :

  • HelloTheWorld est le nom de la route. Il est assez arbitraire, et vous permet juste de vous y retrouver par la suite. La seule contrainte est qu'il soit unique.
  • path correspond à l'URL à laquelle nous souhaitons que notre « Hello World ! » soit accessible. C'est ce qui permet à la route de dire : « Cette URL est pour moi, je prends. »
  • defaults correspond aux paramètres de la route, dont :
    • _controller, qui correspond à l'action (ici, « index ») que l'on veut exécuter et au contrôleur (ici, « Blog ») que l'on va appeler (un contrôleur peut contenir plusieurs actions, c'est-à-dire plusieurs pages).

Ne vous inquiétez pas, un chapitre complet est consacré au routeur et vous permettra de jouer avec. Pour l'instant ce fichier nous permet juste d'avancer. Mais avant d'aller plus loin, penchons-nous sur la valeur que l'on a donnée à _controller : « SdzBlogBundle:Blog:index ». Cette valeur se découpe en suivant les deux-points (« : ») :

  • « SdzBlogBundle » est le nom de notre bundle, celui dans lequel Symfony2 ira chercher le contrôleur.
  • « Blog » est le nom du contrôleur à ouvrir. En terme de fichier, cela correspond à controller/BlogController.php dans le répertoire du bundle. Dans notre cas, nous avons src/Sdz/BlogBundle/controller/BlogController.php comme chemin absolu.
  • « index » est le nom de la méthode à exécuter au sein du contrôleur.

2. Informons Symfony2 que nous avons des routes pour lui

On l'a vu précédemment, grâce au bon travail du générateur, Symfony2 est déjà au courant du fichier de routes de notre bundle. Mais ce n'est pas par magie ! Il faut que vous sachiez comment tout cela s'imbrique. Ouvrez le fichier de configuration globale de notre application : app/config/config.yml. Dans ce fichier, il y a plein de valeurs, mais la section qui nous intéresse est la section router, à la ligne 9 que je vous remets ici :

1
2
3
4
5
# app/config/config.yml

    router:
        resource: "%kernel.root_dir%/config/routing.yml"
        strict_parameters: %kernel.debug%

Cette section indique au routeur qu'il doit chercher les routes dans le fichier app/config/routing.yml (%kernel.root_dir% est un paramètre qui vaut « app » dans notre cas). Le routeur va donc se contenter d'ouvrir ce fichier. Ouvrez-le également :

1
2
3
4
5
# app/config/routing.yml

SdzBlogBundle:
    resource: "@SdzBlogBundle/Resources/config/routing.yml"
    prefix:   /

Outre les commentaires, vous voyez que le générateur a inséré une route spéciale (qui n'a pas de path, mais une resource) qui va importer le fichier de routes de notre bundle.

C'est parce que ce fichier routing.yml était vide (avant la génération du bundle) que l'on avait une erreur « page introuvable » en « prod » : comme il n'y a aucune route définie, Symfony2 nous informe à juste titre qu'aucune page n'existe. Et si le mode « dev » ne nous donnait pas d'erreur, c'est parce qu'il charge le fichier routing_dev.yml et non routing.yml. Et dans ce fichier, allez voir, il y a bien quelques routes définies. Et il y a aussi la ligne qui importe le fichier routing.yml, afin d'avoir les routes du mode « prod » dans le mode « dev » (l'inverse étant bien sûr faux).

Bref, vous n'avez rien à modifier ici, c'était juste pour que vous sachiez que l'import du fichier de routes d'un bundle n'est pas automatique, il se définit dans le fichier de routes global.

Revenons à nos moutons. En fait, on aurait pu ajouter notre route HelloTheWorld directement dans ce fichier routing.yml. Cela aurait fonctionné et cela aurait été plutôt rapide. Mais c'est oublier notre découpage en bundles ! En effet, cette route concerne le bundle du blog, elle doit donc se trouver dans notre bundle et pas ailleurs. N'oubliez jamais ce principe.

Cela permet à notre bundle d'être indépendant : si plus tard nous ajoutons, modifions ou supprimons des routes dans notre bundle, nous ne toucherons qu'au fichier src/Sdz/BlogBundle/Resources/config/routing.yml au lieu de app/config/routing.yml. ;)

Et voilà, il n'y a plus qu'à créer le fameux contrôleur Blog ainsi que sa méthode index !

Créons notre contrôleur

Le rôle du contrôleur

Rappelez-vous ce que nous avons dit sur le MVC :

  • Le contrôleur est la « glu » de notre site ;
  • Il « utilise » tous les autres composants (base de données, formulaires, templates, etc.) pour générer la réponse suite à notre requête ;
  • C'est ici que résidera toute la logique de notre site : si l'utilisateur est connecté et qu'il a le droit de modifier cet article, alors j'affiche le formulaire d'édition des articles de mon blog.

Créons notre contrôleur

1. Le fichier de notre contrôleur Blog

Dans un bundle, les contrôleurs se trouvent dans le répertoire Controller du bundle. Rappelez-vous : dans la route, on a dit qu'il fallait faire appel au contrôleur nommé « Blog ». Le nom des fichiers des contrôleurs doit respecter une convention très simple : il doit commencer par le nom du contrôleur, ici « Blog », suivi du suffixe « Controller ». Au final, on doit donc créer le fichier src/Sdz/BlogBundle/Controller/BlogController.php.

Même si Symfony2 a déjà créé un contrôleur DefaultController pour nous, ce n'est qu'un exemple, on va utiliser le nôtre. Ouvrez donc notre BlogController.php et mettez-y le code suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php

// src/Sdz/BlogBundle/Controller/BlogController.php

namespace Sdz\BlogBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;

class BlogController extends Controller
{
  public function indexAction()
  {
    return new Response("Hello World !");
  }
}

Surprise : allez voir sur http://localhost/Symfony/web/app_dev.php/hello-world ! Même bundle, mais contrôleur différent, on en fait des choses !

Le Hello World s'affiche !

Maintenant, essayons de comprendre rapidement ce fichier :

  • Ligne 5 : on se place dans le namespace des contrôleurs de notre bundle. Rien de bien compliqué, suivez la structure des répertoires dans lequel se trouve le contrôleur.
  • Ligne 7 : notre contrôleur hérite de ce contrôleur de base, il faut donc le charger grâce au use.
  • Ligne 8 : notre contrôleur va utiliser l'objet Response, il faut donc le charger grâce au use.
  • Ligne 10 : le nom de notre contrôleur respecte le nom du fichier pour que l'autoload fonctionne.
  • Ligne 12 : on définit la méthode indexAction(). N'oubliez pas de mettre le suffixe Action derrière le nom de la méthode.
  • Ligne 14 : on crée une réponse toute simple. L'argument de l'objet Response est le contenu de la page que vous envoyez au visiteur, ici « Hello World ! ». Puis on retourne cet objet.

Bon, certes, le rendu n'est pas très joli, mais au moins nous avons atteint l'objectif d'afficher nous-mêmes un « Hello World ! ».

Pourquoi indexAction() ? Je n'ai pas suivi, là.

En effet, il faut savoir que le nom des méthodes des contrôleurs doit respecter une convention. Lorsque, dans la route, on parle de l'action « index », dans le contrôleur on doit définir la méthode indexAction(), c'est-à-dire le nom de l'action suivi du suffixe « Action », tout simplement. Il n'y a pas tellement à réfléchir, c'est une simple convention pour distinguer les méthodes qui vont être appelées par le noyau (les xxxAction()) des autres méthodes que vous pourriez créer au sein de votre contrôleur.

Mais écrire le contenu de sa page de cette manière dans le contrôleur, ce n'est pas très pratique, et en plus de cela on ne respecte pas le modèle MVC. Utilisons donc les templates !

Créons notre template Twig

Les templates avec Twig

Savez-vous ce qu'est un moteur de templates ? C'est un script qui permet d'utiliser des templates, c'est-à-dire des fichiers qui ont pour but d'afficher le contenu de votre page HTML de façon dynamique, mais sans PHP. Comment ? Avec leur langage à eux. Chaque moteur a son propre langage.

Avec Symfony2, nous allons employer le moteur Twig. Voici un exemple de comparaison entre un template simple en PHP (premier code) et un template en « langage Twig » (deuxième code).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
  <head>
    <title>Bienvenue dans Symfony2 !</title>
  </head>
  <body>
    <h1><?php echo $titre_page; ?></h1>

    <ul id="navigation">
      <?php foreach ($navigation as $item) { ?>
        <li>
          <a href="<?php echo $item->getHref(); ?>"><?php echo $item->getTitre(); ?></a>
        </li>
      <?php } ?>
    </ul>
  </body>
</html>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
  <head>
        <title>Bienvenue dans Symfony2 !</title>
  </head>
  <body>
    <h1>{{ titre_page }}</h1>

    <ul id="navigation">
      {% for item in navigation %}
        <li><a href="{{ item.href }}">{{ item.titre }}</a></li>
      {% endfor %}
    </ul>
  </body>
</html>

Ils se ressemblent, soyons d'accord. Mais celui réalisé avec Twig est bien plus facile à lire ! Pour afficher une variable, vous faites juste {{ ma_var }} au lieu de <?php echo $ma_var; ?>.

Le but en fait est de faciliter le travail de votre designer. Un designer ne connaît pas forcément le PHP, ni forcément Twig d'ailleurs. Mais Twig est très rapide à prendre en main, plus rapide à écrire et à lire, et il dispose aussi de fonctionnalités très intéressantes. Par exemple, imaginons que votre designer veuille mettre les titres en lettres majuscules (COMME CECI). Il lui suffit de faire : {{ titre|upper }}, où titre est la variable qui contient le titre d'un article de blog par exemple. C'est plus joli que <?php echo strtoupper($titre); ?>, non ?

Nous verrons dans le chapitre dédié à Twig les nombreuses fonctionnalités que le moteur vous propose et qui vont vous faciliter la vie. En attendant, nous devons avancer sur notre « Hello World ! ».

Utiliser Twig avec Symfony2

Comment utiliser un template Twig depuis notre contrôleur, au lieu d'afficher notre texte tout simple ?

1. Créons le fichier du template

Le répertoire des templates (ou vues) d'un bundle se trouve dans le dossier Resources/views. Ici encore, on ne va pas utiliser le template situé dans le répertoire Default généré par Symfony2. Créons notre propre répertoire Blog et créons notre template index.html.twig dans ce répertoire. Nous avons donc le fichier src/Sdz/BlogBundle/Resources/views/Blog/index.html.twig.

Je vous propose de découper ce nom Blog/index.html.twig :

  • Blog/ est le nom du répertoire. Nous l'avons appelé comme notre contrôleur afin de nous y retrouver (ce n'est pas une obligation, mais c'est fortement recommandé).
  • index est le nom de notre template qui est aussi le nom de la méthode de notre contrôleur (idem, pas obligatoire, mais recommandé).
  • html correspond au format du contenu de notre template. Ici, nous allons y mettre du code HTML, mais vous serez amené à vouloir y mettre du XML ou autre : vous changerez donc cette extension. Cela permet de mieux s'y retrouver.
  • twig est le format de notre template. Ici, nous utilisons Twig comme moteur de templates, mais il est toujours possible d'utiliser des templates PHP.

Revenez à notre template et mettez ce code à l'intérieur :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{# src/Sdz/BlogBundle/Resources/views/Blog/index.html.twig #}

<!DOCTYPE html>
<html>
  <head>
    <title>Bienvenue sur ma première page avec le Site du Zéro !</title>
  </head>
  <body>
    <h1>Hello World !</h1>

    <p>
      Le Hello World est un grand classique en programmation.
      Il signifie énormément, car cela veut dire que vous avez
      réussi à exécuter le programme pour accomplir une tâche simple :
      afficher ce hello world !
    </p>
  </body>
</html>

Dans ce template, nous n'avons utilisé ni variable, ni structure Twig. En fait, c'est un simple fichier contenant du code HTML pur !

2. Appelons ce template depuis le contrôleur

Il ne reste plus qu'à appeler ce template. C'est le rôle du contrôleur, c'est donc au sein de la méthode indexAction() que nous allons appeler le template. Cela se fait très simplement avec la méthode $this->render(). Cette méthode prend en paramètre le nom du template et retourne un objet de type Response avec pour contenu le contenu de notre template. Voici le contrôleur modifié en conséquence :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php

// src/Sdz/BlogBundle/Controller/BlogController.php

namespace Sdz\BlogBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;

class BlogController extends Controller
{
  public function indexAction()
  {
    return $this->render('SdzBlogBundle:Blog:index.html.twig');
  }
}

Nous n'avons modifié que la ligne 14. La convention pour le nom du template est la même que pour le nom du contrôleur, souvenez-vous : NomDuBundle:NomDuContrôleur:NomDeLAction.

Maintenant, retournez sur la page http://localhost/Symfony/web/app_dev.php/hello-world et profitez !

Notre vue Hello World s'affiche bien !

Vous avez des problèmes d'accents ? Faites attention à bien définir l'encodage de vos templates en UTF8 sans BOM.

Notez également l'apparition de la toolbar en bas de la page. Je vous l'avais déjà dit, Symfony2 l'ajoute automatiquement lorsqu'il détecte la balise fermante </body>. C'est pour cela que nous ne l'avions pas tout à l'heure avec notre « Hello World ! » tout simple.

Vous voulez vous amuser un peu avec les variables Twig ? Modifiez la ligne du return du contrôleur pour rajouter un deuxième argument à la méthode render() :

1
2
<?php
return $this->render('SdzBlogBundle:Blog:index.html.twig', array('nom' => 'winzou'));

Puis modifiez votre template en remplaçant la balise <h1> par la suivante :

1
<h1>Hello {{ nom }} !</h1>

C'est tout ! Rechargez la page. Bonjour à vous également. :) On verra plus en détail le passage de variables dans le chapitre dédié à Twig bien évidemment.

Notre objectif : créer un blog

Le fil conducteur : un blog

Tout au long de ce cours, nous construirons un blog. Cela me permet d'utiliser des exemples cohérents entre eux et de vous montrer comment construire un blog de toutes pièces. Bien sûr, libre à vous d'adapter les exemples au projet que vous souhaitez mener, je vous y encourage, même !

Le choix du blog n'est pas très original, mais il permet que l'on se comprenne bien : vous savez déjà ce qu'est un blog, vous comprendrez donc, en théorie, tous les exemples.

Notre blog

Le blog que nous allons créer est très simple. En voici les grandes lignes :

  • Nous aurons des articles auxquels nous attacherons des tags.
  • Nous pourrons lire, écrire, éditer et rechercher des articles.
  • Nous pourrons créer, modifier et supprimer des tags.
  • Au début, nous n'aurons pas de système de gestion des utilisateurs : nous devrons saisir notre nom lorsque nous rédigerons un article. Puis nous rajouterons la couche utilisateur.
  • Au début, il n'y aura pas de système de commentaires. Puis nous ajouterons cette couche commentaire.

Un peu de nettoyage

Avec tous les éléments générés par Symfony2 et les nôtres, il y a un peu de redondance. Vous pouvez donc supprimer joyeusement :

  • Le contrôleur Controller/DefaultController.php ;
  • Les vues dans le répertoire Resources/views/Default ;
  • La route SdzBlogBundle_homepage dans Resources/config/routing.yml.

Ainsi que tout ce qui concerne le bundle AcmeDemoBundle, un bundle de démonstration intégré dans la distribution standard de Symfony2 et dont nous ne nous servirons pas. Supprimez donc :

  • Le répertoire src/Acme ;
  • La ligne 26 du fichier app/AppKernel.php, celle qui active le bundle AcmeDemoBundle ($bundles[] = new Acme\DemoBundle\AcmeDemoBundle()) ;
  • Les 3 premières routes dans le fichier app/config/routing_dev.yml (_welcome, _demo_secured et _demo).

Schéma de développement sous Symfony2

Si vous rafraîchissez la page pour vérifier que tout est bon, il est possible que vous ayez une erreur ! En effet, il faut prendre dès maintenant un réflexe Symfony2 : vider le cache. Car Symfony, pour nous offrir autant de fonctionnalités et être si rapide, utilise beaucoup son cache (des calculs qu'il ne fait qu'une fois puis qu'il stocke). Or après certaines modifications, le cache n'est plus à jour et il se peut que cela génère des erreurs. Deux cas de figure :

  • En mode « prod », c'est simple, Symfony2 ne vide jamais le cache. Cela lui permet de ne faire aucune vérification sur la validité du cache (ce qui prend du temps), et de servir les pages très rapidement à vos visiteurs. La solution : vider le cache à la main à chaque fois que vous faites des changements. Cela se fait grâce à la commande php app/console cache:clear --env=prod.
  • En mode « dev », c'est plus simple et plus compliqué. Lorsque vous modifiez votre code, Symfony reconstruit une bonne partie du cache à la prochaine page que vous chargez. Donc pas forcément besoin de vider le cache. Seulement, comme il ne reconstruit pas tout, il peut parfois apparaître des bugs. Dans ce cas, un petit php app/console cache:clear résout le problème en trois secondes !

Parfois, il se peut que la commande cache:clear génère des erreurs lors de son exécution. Dans ce cas, essayez de relancer la commande, parfois une deuxième passe peut résoudre les problèmes. Dans le cas contraire, supprimez le cache à la main en supprimant simplement le répertoire app/cache/dev (ou app/cache/prod suivant l’environnement).

Typiquement, un schéma classique de développement est le suivant :

  • Je fais des changements, je teste ;
  • Je fais des changements, je teste ;
  • Je fais des changements, je teste : ça ne marche pas, je vide le cache : ça marche ;
  • Je fais des changements, je teste ;
  • Je fais des changements, je teste ;
  • Je fais des changements, je teste : ça ne marche pas, je vide le cache : ça marche ;
  • En fin de journée, j'envoie tout sur le serveur de production, je vide obligatoirement le cache, je teste : ça marche.

Évidemment, quand je dis « je teste : ça ne marche pas », j'entends « ça devrait marcher et l'erreur rencontrée est étrange ». Si vous faites une erreur dans votre propre code, ce n'est pas un cache:clear qui va la résoudre ! :p

Pour conclure

Et voilà, nous avons créé une page de A à Z ! Voici plusieurs remarques sur ce chapitre.

D'abord, ne vous affolez pas si vous n'avez pas tout compris. Le but de ce chapitre était de vous donner une vision globale d'une page Symfony2. Vous avez des notions de bundles, de routes, de contrôleurs et de templates : vous savez presque tout ! Il ne reste plus qu'à approfondir chacune de ces notions, ce que nous ferons dès le prochain chapitre.

Ensuite, sachez que tout n'est pas à refaire lorsque vous créez une deuxième page. Je vous invite là, maintenant, à créer une page /byebye-world et voyez si vous y arrivez. Dans le cas contraire, relisez ce chapitre, puis si vous ne trouvez pas votre erreur, n'hésitez pas à poser votre question sur le forum PHP, d'autres Zéros qui sont passés par là seront ravis de vous aider. :)

Sur le forum, pensez à mettre le tag [Symfony2] dans le titre de votre sujet, afin de s'y retrouver. :)

Enfin, le code source final du blog que nous allons construire ensemble est disponible à l'adresse suivante : www.tutoriel-symfony2.fr/livre/codesource. Il est un peu tôt pour que vous alliez le voir, car il contient le code final alors que nous allons le construire pas à pas grâce à ce cours. Cependant, il peut être d'une bonne aide, allez y jeter un oeil de temps en temps. ;)

Allez, préparez-vous pour la suite, les choses sérieuses commencent !


En résumé

  • Le rôle du routeur est de déterminer quel route utiliser pour la requête courante.
  • Le rôle d'une route est d'associer une URL à une action du contrôleur.
  • Le rôle du contrôleur est de retourner au noyau un objet Response, qui contient la réponse HTTP à envoyer à l'internaute (page HTML ou redirection).
  • Le rôle des vues est de mettre en forme les données que le contrôleur lui donne, afin de former une page HTML, un flux RSS, un e-mail, etc.