Version en ligne

Tutoriel : Développez votre site web avec le framework Symfony2

Table des matières

Développez votre site web avec le framework Symfony2
Symfony2, un framework PHP
Qu'est-ce qu'un framework ?
Qu'est-ce que Symfony2 ?
Télécharger Symfony2
Vous avez dit Symfony2 ?
L'architecture des fichiers
L'architecture conceptuelle
Symfony2 et ses bundles
Utilisons la console pour créer un bundle
Utilisation de la console
Créons notre bundle
Mon premier « Hello World ! » avec Symfony2
Créons notre route
Créons notre contrôleur
Créons notre template Twig
Notre objectif : créer un blog
Le routeur de Symfony2
Le fonctionnement
Les routes de base
Les routes avancées
Générer des URL
Application : les routes de notre blog
Les contrôleurs avec Symfony2
Le rôle du contrôleur
Manipuler l'objet Request
Manipuler l'objet Response
Les différents services
Application : le contrôleur de notre blog
Le moteur de templates Twig
Les templates Twig
Afficher des variables
Structures de contrôle et expressions
Hériter et inclure des templates
Application : les templates de notre blog
Installer un bundle grâce à Composer
Composer, qu'est-ce que c'est ?
Installer Composer et Git
Installer un bundle grâce à Composer
Les services, théorie et création
Pourquoi utiliser des services ?
Utiliser un service en pratique
Créer un service simple
Créer un service avec des arguments
La couche métier : les entités
Notions d'ORM : fini les requêtes, utilisons des objets
Créer une première entité avec Doctrine2
Tout sur le mapping !
Manipuler ses entités avec Doctrine2
Matérialiser les tables en base de données
Enregistrer ses entités avec l'EntityManager
Récupérer ses entités avec un EntityRepository
Les relations entre entités avec Doctrine2
Présentation
Relation One-To-One
Relation Many-To-One
Relation Many-To-Many
Relation Many-To-Many avec attributs
Les relations bidirectionnelles
Récupérer ses entités avec Doctrine2
Le rôle des repositories
Les méthodes de récupération de base
Les méthodes de récupération personnelles
Utiliser les jointures dans nos requêtes
Application : les entités de notre blog
Les évènements et extensions Doctrine
Les évènements Doctrine
Les extensions Doctrine
TP : Les entités de notre blog
Synthèse des entités
Adaptation du contrôleur
Amélioration du contrôleur
Créer des formulaires avec Symfony2
Gestion des formulaires
Externaliser la définition de ses formulaires
Les formulaires imbriqués
Aller plus loin avec les formulaires
Le type de champ File pour envoyer des fichiers
Application : les formulaires de notre blog
Validez vos données
Pourquoi valider des données ?
Définir les règles de validation
Déclencher la validation
Encore plus de règles de validation
Valider selon nos propres contraintes
Sécurité et gestion des utilisateurs
Authentification et autorisation
Première approche de la sécurité
Gestion des autorisations avec les rôles
Utiliser des utilisateurs de la base de données
Utiliser FOSUserBundle
Les services, utilisation poussée
Les tags sur les services
Dépendances optionnelles : les calls
Les champs d'application, ou scopes
Les services courants de Symfony2
Le gestionnaire d'évènements de Symfony2
Des évènements ? Pour quoi faire ?
Écouter les évènements
Les évènements Symfony2… et les nôtres !
Allons un peu plus loin
Traduire son site
Introduction à la traduction
Bonjour le monde
Le catalogue
Récupérer la locale de l'utilisateur
Organiser vos catalogues
Traductions dépendantes de variables
Utiliser des ParamConverters pour convertir les paramètres de requêtes
Théorie : pourquoi un ParamConverter ?
Pratique : utilisation des ParamConverters existants
Aller plus loin : créer ses propres ParamConverters
Personnaliser les pages d'erreur
Théorie : remplacer les vues d'un bundle
Pratique : remplacer les templates Exception de TwigBundle
Utiliser Assetic pour gérer les codes CSS et JS de votre site
Théorie : entre vitesse et lisibilité, pourquoi choisir ?
Pratique : Assetic à la rescousse !
Utiliser la console directement depuis le navigateur
Théorie : le composant Console de Symfony2
Pratique : utiliser un ConsoleBundle
Déployer son site Symfony2 en production
Préparer son application en local
Vérifier et préparer le serveur de production
Déployer votre application

Développez votre site web avec le framework Symfony2

Vous savez déjà faire des sites internet ? Vous maîtrisez votre code, mais n'êtes pas totalement satisfait ? Vous avez trop souvent l'impression de réinventer la roue ?

Alors ce tutoriel est fait pour vous !

Symfony2 est un puissant framework qui va vous permettre de réaliser des sites complexes rapidement, mais de façon structurée et avec un code clair et maintenable. En un mot : le paradis du développeur !

Ce tutoriel est un tutoriel pour débutants sur Symfony2, vous n'avez besoin d'aucune notion sur les frameworks pour l'aborder, nous allons les découvrir ensemble. Cependant, il est fortement conseillé :

Symfony2, un framework PHP

Qu'est-ce qu'un framework ?

Alors, vous avez décidé de vous lancer dans Symfony2 ? Parfait, vous ne le regretterez pas ! Tout au long de ce cours, nous apprendrons à utiliser ce framework, et vous comprendrez petit à petit la puissance de cet outil. Mais tout d'abord, commençons par les bases, et voyons précisément quels sont les objectifs et les limites d'un framework tel que Symfony2.

Dans ce chapitre, nous allons découvrir pourquoi Symfony2 est un bon choix pour votre application web. Une boîte à outils faite en PHP qui a pour but de vous simplifier la vie, c'est toujours sympa, non ? Allons-y !

Qu'est-ce qu'un framework ?

Symfony2, un framework PHP Qu'est-ce que Symfony2 ?

L'objectif d'un framework

L'objectif de ce chapitre n'est pas de vous fournir toutes les clés pour concevoir un framework, mais suffisamment pour pouvoir en utiliser un. On exposera rapidement l'intérêt, les avantages et les inconvénients de l'utilisation d'un tel outil.

Définition

Le mot « framework » provient de l'anglais « frame » qui veut dire « cadre » en français, et « work » qui signifie « travail ». Littéralement, c'est donc un « cadre de travail ». Vous voilà avancés, hein ? :p Concrètement, c'est un ensemble de composants qui servent à créer les fondations, l'architecture et les grandes lignes d'un logiciel. Il existe des centaines de frameworks couvrant la plupart des langages de programmation. Ils sont destinés au développement de sites web ou bien à la conception de logiciels.

Un framework est une boîte à outils conçue par un ou plusieurs développeurs à destination d'autres développeurs. Contrairement à certains scripts tels que WordPress, Dotclear ou autres, un framework n'est pas utilisable tel quel. Il n'est pas fait pour être utilisé par les utilisateurs finaux. Le développeur qui se sert d'un framework a encore du boulot à fournir, d'où ce cours !

Objectif d'un framework

L'objectif premier d'un framework est d'améliorer la productivité des développeurs qui l'utilisent. Plutôt sympa, non ? Souvent organisé en différents composants, un framework offre la possibilité au développeur final d'utiliser tel ou tel composant pour lui faciliter le développement, et lui permet ainsi de se concentrer sur le plus important.

Prenons un exemple concret. Il existe dans Symfony2 un composant qui gère les formulaires HTML : leur affichage, leur validation, etc. Le développeur qui l'utilise se concentre sur l'essentiel dans son application : chaque formulaire effectue une action, et c'est cette action qui est importante, pas les formulaires. Étendez ce principe à toute une application ou tout un site internet, et vous comprenez l'intérêt d'un framework ! Autrement dit, le framework s'occupe de la forme et permet au développeur de se concentrer sur le fond.

Pesons le pour et le contre

Comme tout bon développeur, lorsqu'on veut utiliser un nouvel outil, on doit en peser le pour et le contre pour être sûr de faire le bon choix !

Les pour

L'avantage premier est donc, on vient de le voir, le gain en productivité. Mais il en existe bien d'autres ! On peut les classer en plusieurs catégories : le code, le travail et la communauté.

Tout d'abord, un framework va vous aider à réaliser un « bon code ». Par « bon code », j'entends qu'il vous incite, de par sa propre architecture, à bien organiser votre code. Et un code bien organisé est un code facilement maintenable et évolutif ! De plus, un framework offre des briques prêtes à être utilisées (le composant Formulaire de Symfony2 par exemple), ce qui vous évite de réinventer la roue, et surtout qui vous permet d'utiliser des briques puissantes et éprouvées. En effet, ces briques sont développées par des équipes de développeurs chevronnés, elles sont donc très flexibles et très robustes. Vous économisez ainsi des heures de développement !

Ensuite, un framework améliore la façon dont vous travaillez. En effet, dans le cas d'un site internet, vous travaillez souvent avec d'autres développeurs PHP et un designer. Un framework vous aide doublement dans ce travail en équipe. D'une part, un framework utilise presque toujours l'architecture MVC ; on en reparlera, mais sachez pour le moment que c'est une façon d'organiser son code qui sépare le code PHP du code HTML. Ainsi, votre designer peut travailler sur des fichiers différents des vôtres, fini les problèmes d'édition simultanée d'un même fichier ! D'autre part, un framework a une structure et des conventions de code connues. Ainsi, vous pouvez facilement recruter un autre développeur : s'il connaît déjà le framework en question, il s'intégrera très rapidement au projet.

Enfin, le dernier avantage est la communauté soutenant chaque framework. C'est elle qui fournit les tutoriaux ou les cours (comme celui que vous lisez !), de l'aide sur les forums, et bien sûr les mises à jour du framework. Ces mises à jour sont très importantes : imaginez que vous codiez vous-mêmes tout ce qui est connexion utilisateur, session, moteur de templates, etc. Comme il est impossible de coder sans bugs, vous devriez logiquement corriger chaque bug déclaré sur votre code. Maintenant, imaginez que toutes les briques de votre site, qui ne sont pas forcément votre tasse de thé, soient fournies par le framework. À chaque fois que vous ou les milliers d'autres utilisateurs du framework trouverez un bug, les développeurs et la communauté s'occuperont de le corriger, et vous n'aurez plus qu'à suivre les mises à jour. Un vrai paradis !

Il existe plein d'autres avantages que je ne vais pas vous détailler, mais un framework, c'est aussi :

Les contre

Vous vous en doutez, avec autant d'avantages il y a forcément des inconvénients. Eh bien, figurez-vous qu'il n'y en a pas tant que ça !

S'il ne fallait en citer qu'un, cela serait évidemment la courbe d'apprentissage qui est plus élevée. En effet, pour maîtriser un framework, il faut un temps d'apprentissage non négligeable. Chaque brique qui compose un framework a sa complexité propre qu'il vous faudra appréhender.

Notez également que pour les frameworks les plus récents, tels que Symfony2 justement, il faut également être au courant des dernières nouveautés de PHP. Je pense notamment à la programmation orientée objet et aux namespaces. De plus, connaître certaines bonnes pratiques telles que l'architecture MVC est un plus.

Mais rien de tout cela ne doit vous effrayer ! Voyez l'apprentissage d'un framework comme un investissement : il y a un certain effort à fournir au début, mais les résultats se récoltent ensuite sur le long terme !

Alors, convaincus ?

J'espère vous avoir convaincus que les pour l'emportent largement sur les contre. Si vous êtes prêts à relever le défi aujourd'hui pour être plus productifs demain, alors ce cours est fait pour vous !

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Symfony2, un framework PHP Qu'est-ce que Symfony2 ?

Qu'est-ce que Symfony2 ?

Qu'est-ce qu'un framework ? Télécharger Symfony2

Un framework

Symfony2 est donc un framework PHP. Bien sûr, il en existe d'autres ; pour ne citer que les plus connus : Zend Framework, CodeIgniter, CakePHP, etc. Le choix d'un framework est assez personnel, et doit être adapté au projet engagé. Sans vouloir prêcher pour ma paroisse, Symfony2 est l'un des plus flexibles et des plus puissants.

Un framework populaire

Symfony est très populaire. C'est un des frameworks les plus utilisés dans le monde, notamment dans les entreprises. Il est utilisé par Dailymotion par exemple ! La première version de Symfony est sortie en 2005 et est aujourd'hui toujours très utilisée. Cela lui apporte un retour d'expérience et une notoriété exceptionnels. Aujourd'hui, beaucoup d'entreprises dans le domaine de l'internet (dont Simple IT, l'éditeur du Site du Zéro !) recrutent des développeurs capables de travailler sous ce framework. Ces développeurs pourront ainsi se greffer aux projets de l'entreprise très rapidement, car ils en connaîtront déjà les grandes lignes. C'est un atout si vous souhaitez travailler dans ce domaine. ;)

La deuxième version, que nous étudierons dans ce tutoriel, est sortie en août 2011. Elle est encore jeune, son développement a été fulgurant grâce à une communauté de développeurs dévoués. Bien que différente dans sa conception, cette deuxième version est plus rapide et plus souple que la première. Il y a fort à parier que très rapidement beaucoup d'entreprises s'arracheront les compétences des premiers développeurs Symfony2. Faites-en partie !

Un framework populaire et français

Eh oui, Symfony2, l'un des meilleurs frameworks PHP au monde, est un framework français ! Il est édité par la société SensioLabs, dont le créateur est Fabien Potencier. Mais Symfony2 étant un script open source, il a également été écrit par toute la communauté : beaucoup de Français, mais aussi des développeurs de tous horizons : Europe, États-Unis, etc. C'est grâce au talent de Fabien et à la générosité de la communauté que Symfony2 a vu le jour.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Qu'est-ce qu'un framework ? Télécharger Symfony2

Télécharger Symfony2

Qu'est-ce que Symfony2 ? Vous avez dit Symfony2 ?

Obtenir Symfony2

Il existe de nombreux moyens d'obtenir Symfony2. Nous allons voir ici la méthode la plus simple : télécharger la distribution standard. Pour cela, rien de plus simple, rendez-vous sur le site de Symfony2, rubrique Download, et téléchargez la version « Symfony Standard (.zip) ».

Une fois l'archive téléchargée, décompressez les fichiers dans votre répertoire web habituel, par exemple C:\wamp\www pour Windows ou /var/www pour Linux. Pour la suite du tutoriel, je considérerai que les fichiers sont accessibles à l'URL http://localhost/Symfony. Je vous recommande d'avoir la même adresse, car je ferai ce genre de liens tout au long du tutoriel. ;)

Vérifier votre configuration de PHP

Symfony2 a quelques contraintes par rapport à votre configuration PHP. Par exemple, il ne tourne que sur la version 5.3.2 ou supérieure de PHP. Pour vérifier si votre environnement est compatible, rendez-vous à l'adresse suivante : http://localhost/Symfony/web/config.php. Si vous avez une version adéquate de PHP, vous devriez obtenir la figure suivante.

Mon environnement de travail est compatible avec Symfony2 !
Mon environnement de travail est compatible avec Symfony2 !

En cas d'incompatibilité (version de PHP notamment), Symfony2 vous demande de régler les problèmes avant de continuer. S'il ne vous propose que des recommandations, vous pouvez continuer sans problème. Ce sont des points que je vous conseille de régler, mais qui sont facultatifs.

Je fais un petit aparté pour les lecteurs travaillant sous Linux. Symfony2 a besoin d'écrire dans quelques répertoires, il faut donc bien régler les droits sur les répertoires app/cache et app/logs. Pour cela, placez-vous dans le répertoire Symfony et videz d'abord ces répertoires :

rm -rf app/cache/*
rm -rf app/logs/*

Ensuite, si votre distribution supporte le chmod +a, exécutez ces commandes pour définir les bons droits :

sudo chmod +a "www-data allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs
sudo chmod +a "`whoami` allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs

Si vous rencontrez une erreur avec ces commandes, exécutez les commandes suivantes, qui n'utilisent pas le chmod +a :

sudo setfacl -R -m u:www-data:rwx -m u:`whoami`:rwx app/cache app/logs
sudo setfacl -dR -m u:www-data:rwx -m u:`whoami`:rwx app/cache app/logs

Enfin, si vous ne pouvez pas utiliser les ACL (utilisés dans les commandes précédentes), définissez simplement les droits comme suit :

chmod 777 app/cache
chmod 777 app/logs

Voilà, fin de l'aparté.

Vous pouvez dès à présent exécuter Symfony2, félicitations ! Rendez-vous sur la page http://localhost/Symfony/web/app_dev.php, vous devriez avoir quelque chose ressemblant à la figure suivante.

La page d'accueil de Symfony2
La page d'accueil de Symfony2

Vérifier l'installation de PHP en console

Nous aurons parfois besoin d'exécuter des commandes PHP via la console, pour générer du code ou gérer la base de données. Ce sont des commandes qui vont nous faire gagner du temps (toujours le même objectif !), vérifions donc que PHP est bien disponible en console. Rassurez-vous, je vous indiquerai toujours pas à pas comment les utiliser. :)

Si vous êtes sous Linux ou Mac, vous ne devriez pas avoir de soucis, PHP est bien disponible en console. Si vous êtes sous Windows, rien n'est sûr. Dans tous les cas, vérifiez-le en ouvrant l'invite de commandes pour Windows, ou le terminal pour Linux, et entrez la commande suivante : php -v. Si cette commande vous retourne bien la version de PHP et d'autres informations, c'est tout bon pour vous.

La commande vous affiche une erreur ?

Si vous êtes sous Windows, PHP est bien installé mais Windows ne sait pas où le trouver, il faut juste le lui dire. Voici la démarche à suivre pour régler ce problème :

  1. Allez dans les paramètres système avancés (Démarrer > Panneau de configuration > Système et sécurité > Système > Paramètres système avancés) ;

  2. Cliquez sur le bouton Variables d'environnement… ;

  3. Regardez dans le panneau Variables système ;

  4. Trouvez l'entrée Path (vous devriez avoir à faire descendre l'ascenseur pour le trouver) ;

  5. Double-cliquez sur l'entrée Path ;

  6. Entrez votre répertoire PHP à la fin, sans oublier le point-virgule (;) auparavant. C'est le répertoire dans lequel se trouve le fichier php.exe. Par exemple ;C:\wamp\bin\php\php5.3 ;

  7. Confirmez en cliquant sur OK. Vous devez ensuite redémarrer l'invite de commandes pour prendre en compte les changements.

Si vous êtes sous Linux, vérifiez votre installation de PHP. Vous devez notamment avoir le paquet php5-cli, qui est la version console de PHP.

Dans les deux cas, vérifiez après vos manipulations que le problème est bien résolu. Pour cela, exécutez à nouveau la commande php -v. Elle devrait alors vous afficher la version de PHP.

Et voilà, votre poste de travail est maintenant opérationnel pour développer avec Symfony2 !

En résumé

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Qu'est-ce que Symfony2 ? Vous avez dit Symfony2 ?

Vous avez dit Symfony2 ?

Télécharger Symfony2 L'architecture des fichiers

Dans ce chapitre, nous allons voir comment est organisé Symfony2 à l'intérieur. Nous n'entrerons pas dans les détails, c'est trop tôt, le but est juste d'avoir une vision globale du processus d'exécution d'une page sous Symfony2. Ainsi, vous pourrez comprendre ce que vous faites. C'est mieux, non ? :)

L'architecture des fichiers

Vous avez dit Symfony2 ? L'architecture conceptuelle

On vient d'extraire beaucoup de fichiers, mais sans savoir encore à quoi ils servent. C'est le moment d'éclaircir tout cela !

Liste des répertoires

Ouvrez donc le répertoire dans lequel vous avez extrait les fichiers. Vous pouvez voir qu'il n'y a pas beaucoup de fichiers ici, seulement des répertoires. En effet, tout est bien rangé dans chaque répertoire, il nous faut donc comprendre à quoi ils servent. En voici la liste :

Le répertoire /app

Ce répertoire contient tout ce qui concerne votre site internet… sauf son code source. Assez étrange, me direz-vous. En fait, c'est simplement pour séparer le code source, qui fait la logique de votre site, du reste. Le reste, c'est ce répertoire /app. Et ce reste c'est : la configuration, le cache, les fichiers logs, etc. Ce sont des fichiers qui concernent l'entièreté de votre site, contrairement aux fichiers de code source qui seront découpés par fonctionnalité de votre site. Dans Symfony2, un projet de site internet est une application, simple question de vocabulaire. Le répertoire /app est donc le raccourci pour « application ».

Le répertoire /src

Voici enfin le répertoire dans lequel on mettra le code source ! C'est ici que l'on passera le plus clair de notre temps. Dans ce répertoire, nous organiserons notre code en bundles, des briques de notre application, dont nous verrons la définition plus loin.

Vous pouvez voir que ce répertoire n'est pas vide : il contient en effet quelques fichiers exemples, fournis par Symfony2. Nous les supprimerons plus tard dans ce cours.

Le répertoire /vendor

Ce répertoire contient toutes les bibliothèques externes à notre application. Dans ces bibliothèques externes, j'inclus Symfony2 ! Vous pouvez parcourir ce répertoire, vous y trouverez des bibliothèques comme Doctrine, Twig, SwiftMailer, etc.

Et une bibliothèque, c'est quoi exactement ?

Une bibliothèque est une sorte de boîte noire qui remplit une fonction bien précise, et dont on peut se servir dans notre code. Par exemple, la bibliothèque SwiftMailer permet d'envoyer des e-mails. On ne sait pas comment elle fonctionne (principe de la boîte noire), mais on sait comment s'en servir : on pourra donc envoyer des e-mails très facilement, juste en apprenant rapidement à utiliser la bibliothèque.

Le répertoire /web

Ce répertoire contient tous les fichiers destinés à vos visiteurs : images, fichiers CSS et JavaScript, etc. Il contient également le contrôleur frontal (app.php), dont nous parlerons juste après.

En fait, c'est le seul répertoire qui devrait être accessible à vos visiteurs. Les autres répertoires ne sont pas censés être accessibles (ce sont vos classes, elles vous regardent vous, pas vos visiteurs), c'est pourquoi vous y trouverez des fichiers .htaccess interdisant l'accès depuis l'extérieur. On utilisera donc toujours des URL du type http://localhost/Symfony/web/… au lieu de simplement http://localhost/Symfony/… .

À retenir

Retenez donc que nous passerons la plupart de notre temps dans le répertoire /src, à travailler sur nos bundles. On touchera également pas mal au répertoire /app pour configurer notre application. Et lorsque nous installerons des bundles téléchargés, nous le ferons dans le répertoire /vendor.

Le contrôleur frontal

Définition

Le contrôleur frontal (front controller, en anglais) est le point d'entrée de votre application. C'est le fichier par lequel passent toutes vos pages. Vous devez surement connaître le principe d'index.php et des pseudo-frames (avec des URL du type index.php?page=blog) ; eh bien, cet index.php est un contrôleur frontal. Dans Symfony2, le contrôleur frontal se situe dans le répertoire /web, il s'agit de app.php ou app_dev.php.

Pourquoi y a-t-il deux contrôleurs frontaux ? Normalement, c'est un fichier unique qui gère toutes les pages, non ?

Vous avez parfaitement raison… pour un code classique ! Mais nous travaillons maintenant avec Symfony2, et son objectif est de nous faciliter le développement. C'est pourquoi Symfony2 propose un contrôleur frontal pour nos visiteurs, app.php, et un contrôleur frontal lorsque nous développons, app_dev.php. Ces deux contrôleurs frontaux, fournis par Symfony2 et prêts à l'emploi, définissent en fait deux environnements de travail.

Deux environnements de travail

L'objectif est de répondre au mieux suivant la personne qui visite le site :

Vous voyez la différence ? À chacun ses besoins, et Symfony2 compte bien tous les remplir. C'est pourquoi il offre plusieurs environnements de travail :

Essayez-les ! Allez sur http://localhost/Symfony/web/app_dev.php et vous verrez une barre d'outils en bas de votre écran, contenant nombre d'informations utiles au développement. Allez sur http://localhost/Symfony/web/app.php et vous obtiendrez... une erreur 404. :p En effet, aucune page n'est définie par défaut pour le mode « prod ». Nous les définirons plus tard, mais notez que c'est une « belle » erreur 404, aucun terme barbare n'est employé pour la justifier. ^^

Pour voir le comportement du mode « dev » en cas d'erreur, essayez aussi d'aller sur une page qui n'existe pas. Vous avez vu ce que donne une page introuvable en mode « prod », mais allez maintenant sur /app_dev.php/pagequinexistepas. La différence est claire : le mode « prod » nous dit juste « page introuvable » alors que le mode « dev » nous donne plein d'informations sur l'origine de l'erreur, indispensables pour la corriger.

C'est pourquoi, dans la suite du tutoriel, nous utiliserons toujours le mode « dev », en passant donc par app_dev.php. Bien sûr, lorsque votre site sera opérationnel et que des internautes pourront le visiter, il faudra leur faire utiliser le mode « prod ». Mais nous n'en sommes pas encore là.

Et comment savoir quelles erreurs surviennent en mode production si elles ne s'affichent pas ?

C'est une bonne question, en effet si par malheur une erreur intervient pour l'un de vos visiteurs, il ne verra aucun message et vous non plus, une vraie galère pour déboguer ! En réalité, si les erreurs ne sont pas affichées, elles sont bien stockées dans un fichier. Allez jeter un œil au fichier app/logs/prod.log qui contient plein d'informations sur les requêtes effectuées en mode production, dont les erreurs.

Concrètement, qu'est-ce que contrôle le contrôleur frontal ?

Très bonne question. Pour cela, rien de tel que… d'ouvrir le fichier app.php. Ouvrez-le et vous constaterez qu'il ne fait pas grand-chose. En effet, le but du contrôleur frontal n'est pas de faire quelque chose, mais d'être un point d'entrée de notre application. Il se limite donc à appeler le noyau (Kernel) de Symfony2 en disant « On vient de recevoir une requête, transforme-la en réponse s'il-te-plaît. »

Ici, voyez le contrôleur frontal comme un fichier à nous (il est dans notre répertoire /web), et le Kernel comme un composant Symfony2, une boîte noire (il est dans le répertoire /vendor). Vous voyez comment on a utilisé notre premier composant Symfony2 : on a délégué la gestion de la requête au Kernel. Bien sûr, ce Kernel aura besoin de nous pour savoir quoi exécuter comme code, mais il gère déjà plusieurs choses que nous avons vues : la gestion des erreurs, l'ajout de la toolbar en bas de l'écran, etc. On n'a encore rien fait, et pourtant on a déjà gagné du temps !

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Vous avez dit Symfony2 ? L'architecture conceptuelle

L'architecture conceptuelle

L'architecture des fichiers Symfony2 et ses bundles

On vient de voir comment sont organisés les fichiers de Symfony2. Maintenant, il s'agit de comprendre comment s'organise l'exécution du code au sein de Symfony2.

Architecture MVC

Vous avez certainement déjà entendu parler de ce concept. Sachez que Symfony2 respecte bien entendu cette architecture MVC. Je ne vais pas entrer dans ses détails, car il y a déjà un super cours sur le Site du Zéro, mais en voici les grandes lignes.

MVC signifie « Modèle / Vue / Contrôleur ». C'est un découpage très répandu pour développer les sites internet, car il sépare les couches selon leur logique propre :

Au final, si vous avez bien compris, le contrôleur ne contient que du code très simple, car il se contente d'utiliser des modèles et des vues en leur attribuant des tâches précises. Il agit un peu comme un chef d'orchestre, qui n'agite qu'une baguette alors que ses musiciens jouent des instruments complexes.

Parcours d'une requête dans Symfony2

Afin de bien visualiser tous les acteurs que nous avons vus jusqu'à présent, je vous propose un schéma du parcours complet d'une requête dans Symfony2 :

Parcours complet d'une requête dans Symfony2
Parcours complet d'une requête dans Symfony2

En le parcourant avec des mots, voici ce que cela donne :

  1. Le visiteur demande la page /blog ;

  2. Le contrôleur frontal reçoit la requête, charge le Kernel et la lui transmet ;

  3. Le Kernel demande au Routeur quel contrôleur exécuter pour l'URL /blog. Ce Routeur est un composant Symfony2 qui fait la correspondance entre URL et contrôleurs, nous l'étudierons bien sûr dans un prochain chapitre. Le Routeur fait donc son travail, et dit au Kernel qu'il faut exécuter le contrôleur SdzBlog ;

  4. Le Kernel exécute donc ce contrôleur. Le contrôleur demande au modèle Article la liste des articles, puis la donne à la vue ListeArticles pour qu'elle construise la page HTML et la lui retourne. Une fois cela fini, le contrôleur envoie au visiteur la page HTML complète.

J'ai mis des couleurs pour distinguer les points où l'on intervient. En vert donc, les contrôleur, modèle et vue, c'est ce qu'on devra développer nous-mêmes. En orange donc, le Kernel et le Routeur, c'est ce qu'on devra configurer. On ne touchera pas au contrôleur frontal, en gris.

Maintenant, il ne nous reste plus qu'à voir comment organiser concrètement notre code et sa configuration.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

L'architecture des fichiers Symfony2 et ses bundles

Symfony2 et ses bundles

L'architecture conceptuelle Utilisons la console pour créer un bundle

La découpe en bundles

Le concept

Vous avez déjà croisé ce terme de bundle quelques fois depuis le début du cours, mais qu'est-ce qui se cache derrière ce terme ?

Pour faire simple, un bundle est une brique de votre application. Symfony2 utilise ce concept novateur qui consiste à regrouper dans un même endroit, le bundle, tout ce qui concerne une même fonctionnalité. Par exemple, on peut imaginer un bundle « Blog » dans notre site, qui regrouperait les contrôleurs, les modèles, les vues, les fichiers CSS et JavaScript, etc. Tout ce qui concerne directement la fonctionnalité blog de notre site.

Cette organisation permet de découper naturellement nos fonctionnalités, et ainsi de ranger chaque fichier à sa place. Un fichier JavaScript n'est utilisé que sur le bundle Blog ? Mettez-le dans le bundle Blog ! Bien évidemment, au sein d'un bundle, il faut retrouver également une architecture bien définie, nous l'étudierons juste après.

Des exemples

Pour mieux visualiser, je vous propose quelques bons exemples de bundles possibles :

Et ces bundles, parce qu'ils respectent des règles communes, vont fonctionner ensemble. Par exemple, un bundle Forum et un bundle Utilisateur devront s'entendre : dans un forum, ce sont des utilisateurs qui interagissent. ;)

L'intérêt

Une question à toujours se poser : quel est l'intérêt de ce que l'on est en train de faire ?

En plus d'organiser votre code par fonctionnalités, la découpe en bundles permet l'échange de bundles entre applications ! Cela signifie que vous pouvez développer une fonctionnalité, puis la partager avec d'autres développeurs ou encore la réutiliser dans un de vos autres projets. Et bien entendu, cela marche dans l'autre sens : vous pouvez installer dans votre projet des bundles qui ont été développés par d'autres !

Le principe même des bundles offre donc des possibilités infinies ! Imaginez le nombre de fonctionnalités classiques sur un site internet, que vous n'aurez plus à développer vous-mêmes. Vous avez besoin d'un livre d'or ? il existe sûrement un bundle. Vous avez besoin d'un blog ? il existe sûrement un bundle, etc.

Les bundles de la communauté

Presque tous les bundles de la communauté Symfony2 sont regroupés sur un même site : http://knpbundles.com/. Il en existe beaucoup, et pour n'en citer que quelques-uns :

Je vous conseille vivement de passer sur http://knpbundles.com/ avant de commencer à développer un bundle. S'il en existe déjà un et qu'il vous convient, il serait trop bête de réinventer la roue. ;) Bien sûr, il faut d'abord apprendre à installer un bundle externe, patience !

La structure d'un bundle

Un bundle contient tout : contrôleurs, vues, modèles, classes personnelles, etc. Bref, tout ce qu'il faut pour remplir la fonction du bundle. Évidemment, tout cela est organisé en dossiers afin que tout le monde s'y retrouve. Voici la structure d'un bundle à partir de son répertoire de base, vous pouvez en voir l'illustration grâce au bundle exemple fourni par défaut dans src/Acme/DemoBundle/ :

/Controller          | Contient vos contrôleurs
/DependencyInjection | Contient des informations sur votre bundle (chargement automatique de la configuration par exemple)
/Entity              | Contient vos modèles
/Form                | Contient vos éventuels formulaires
/Resources
-- /config             | Contient les fichiers de configuration de votre bundle (nous placerons les routes ici, par exemple)
-- /public             | Contient les fichiers publics de votre bundle : fichiers CSS et JavaScript, images, etc.
-- /views              | Contient les vues de notre bundle, les templates Twig
/Tests               | Contient vos éventuels tests unitaires et fonctionnels. Nous développerons sans faire de tests au début.

La structure est assez simple au final, retenez-la bien. Sachez qu'elle n'est pas du tout fixe, vous pouvez créer tous les dossiers que vous voulez pour mieux organiser votre code. Mais cette structure conventionnelle permet à d'autres développeurs de comprendre rapidement votre bundle. Bien entendu, je vous guiderai pour chaque création de fichier. ;)

En résumé

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

L'architecture conceptuelle Utilisons la console pour créer un bundle

Utilisons la console pour créer un bundle

Symfony2 et ses bundles Utilisation de la console

Dans ce chapitre, nous allons créer notre premier bundle, juste histoire d'avoir la structure de base de notre code futur. Mais nous ne le ferons pas n'importe comment : nous allons générer le bundle en utilisant une commande Symfony2 en console ! L'objectif est de découvrir la console utilement.

Utilisation de la console

Utilisons la console pour créer un bundle Créons notre bundle

Tout d'abord, vous devez savoir une chose : Symfony2 intègre des commandes disponibles non pas via le navigateur, mais via l'invite de commandes (sous Windows) ou le terminal (sous Linux). Il existe pas mal de commandes qui vont nous servir assez souvent lors du développement, apprenons donc dès maintenant à utiliser cette console !

Les outils disponibles en ligne de commande ont pour objectif de nous faciliter la vie. Ce n'est pas un obscur programme pour les geeks amoureux de la console ! Vous pourrez à partir de là générer une base de code source pour certains fichiers récurrents, vider le cache, ajouter des utilisateurs par la suite, etc. N'ayez pas peur de cette console.

Sous Windows

Lancez l'invite de commandes : Menu Démarrer > Programmes > Accessoires > Invite de commandes. Une fenêtre semblable à la figure suivante devrait apparaître.

La console Windows
La console Windows

Puis placez-vous dans le répertoire où vous avez mis Symfony2, en utilisant la commande Windows cd (je vous laisse adapter la commande) :

Microsoft Windows [version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. Tous droits réservés.

C:\Users\winzou>cd ../../wamp/www/Symfony

C:\wamp\www\Symfony>_

On va exécuter des fichiers PHP depuis cette invite de commandes. En l'occurrence, c'est le fichier app/console (ouvrez-le, c'est bien du PHP) que nous allons exécuter. Pour cela, il faut lancer la commande PHP avec le nom du fichier en argument : php app/console. C'est parti :

C:\wamp\www\Symfony>php app/console

Symfony version 2.2.0-RC2 - app/dev/debug

Usage:
  [options] command [arguments]

Options:
  --help           -h Display this help message.
  --quiet          -q Do not output any message.
  --verbose        -v Increase verbosity of messages.
  --version        -V Display this application version.
  --ansi              Force ANSI output.
  --no-ansi           Disable ANSI output.
  --no-interaction -n Do not ask any interactive question.
  --shell          -s Launch the shell.
  --process-isolation    Launch commands from shell as a separate processes.
  --env            -e The Environment name.
  --no-debug          Switches off debug mode.

Et voila, vous venez d'exécuter une commande Symfony ! Celle-ci ne fait pas grand-chose, c'était juste un entraînement.

Sous Linux et Mac

Ouvrez le terminal. Placez-vous dans le répertoire où vous avez mis Symfony2, probablement /var/www pour Linux ou /user/sites pour Mac. Le fichier que nous allons exécuter est app/console, il faut donc lancer la commande php app/console. Je ne vous fais pas de capture d'écran, j'imagine que vous savez le faire !

À quoi ça sert ?

Une très bonne question, qu'il faut toujours se poser. :) La réponse est très simple : à nous simplifier la vie !

Depuis cette console, on pourra par exemple créer une base de données, vider le cache, ajouter ou modifier des utilisateurs (sans passer par phpMyAdmin !), etc. Mais ce qui nous intéresse dans ce chapitre, c'est la génération de code.

En effet, pour créer un bundle, un modèle ou un formulaire, le code de départ est toujours le même. C'est ce code-là que le générateur va écrire pour nous. Du temps de gagné !

Comment ça marche ?

Comment Symfony2, un framework pourtant écrit en PHP, peut-il avoir des commandes en console ?

Vous devez savoir que PHP peut s'exécuter depuis le navigateur, mais également depuis la console. En fait, côté Symfony2, tout est toujours écrit en PHP, il n'y a rien d'autre. Pour en être sûrs, ouvrez le fichier app/console :

<?php

require_once __DIR__.'/bootstrap.php.cache';
require_once __DIR__.'/AppKernel.php';

use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;

$input = new ArgvInput();
$env = $input->getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev');
$debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(array('--no-debug', '')) && $env !== 'prod';

$kernel = new AppKernel($env, $debug);
$application = new Application($kernel);
$application->run();

Vous ne remarquez rien ? Il ressemble beaucoup au contrôleur frontal app.php ! En fait, il fait presque la même chose, il inclut les mêmes fichiers, et charge également le Kernel. Mais il définit la requête comme venant de la console, ce qui exécute du code différent par la suite. On pourra nous aussi écrire du code qui sera exécuté non pas depuis le navigateur (comme les contrôleurs habituels), mais depuis la console. Rien ne change pour le code, si ce n'est que l'affichage ne peut pas être en HTML bien évidemment.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Utilisons la console pour créer un bundle Créons notre bundle

Créons notre bundle

Utilisation de la console Mon premier « Hello World ! » avec Symfony2

Tout est bundle

Rappelez-vous : dans Symfony2, chaque partie de votre site est un bundle. Pour créer notre première page, il faut donc d'abord créer notre premier bundle. Rassurez-vous, créer un bundle est extrêmement simple avec le générateur. Démonstration !

Exécuter la bonne commande

Comme on vient de l'apprendre, exécutez la commande php app/console generate:bundle.

1. Choisir le namespace

Symfony2 vous demande le namespace de votre bundle :

C:\wamp\www\Symfony>php app/console generate:bundle


  Welcome to the Symfony2 bundle generator



Your application code must be written in bundles. This command helps
you generate them easily.

Each bundle is hosted under a namespace (like Acme/Bundle/BlogBundle).
The namespace should begin with a "vendor" name like your company name, your
project name, or your client name, followed by one or more optional category
sub-namespaces, and it should end with the bundle name itself
(which must have Bundle as a suffix).

See http://symfony.com/doc/current/cookbook/bundles/best_practices.html#index-1
for more
details on bundle naming conventions.

Use / instead of \ for the namespace delimiter to avoid any problem.

Bundle namespace:_

Vous pouvez nommer votre namespace comme bon vous semble, il faut juste qu'il se termine par le suffixe « Bundle ». Par convention, on le compose de trois parties. Nous allons nommer notre namespace « Sdz\BlogBundle ». Explications :

  1. « Sdz » est le namespace racine : il vous représente. Vous pouvez mettre votre pseudo, le nom de votre site ou ce que vous voulez ;

  2. « Blog » est le nom du bundle en lui-même : il définit ce que fait le bundle. Ici, nous créons un blog, nous l'avons donc simplement appelé « Blog » ;

  3. « Bundle » est le suffixe obligatoire.

Entrez donc dans la console Sdz/BlogBundle, avec des slashes juste pour cette fois pour les besoins de la console, mais un namespace comprend bien des anti-slashes.

2. Choisir le nom

Symfony2 vous demande le nom de votre bundle :

Bundle namespace: Sdz/BlogBundle

In your code, a bundle is often referenced by its name. It can be the
concatenation of all namespace parts but it's really up to you to come
up with a unique name (a good practice is to start with the vendor name).
Based on the namespace, we suggest SdzBlogBundle.

Bundle name [SdzBlogBundle]:_

Par convention, on nomme le bundle de la même manière que le namespace, sans les slashes. On a donc : SdzBlogBundle. C'est ce que Symfony2 vous propose par défaut (la valeur entre les crochets), appuyez donc simplement sur Entrée. Retenez ce nom : par la suite, quand on parlera du nom du bundle, cela voudra dire ce nom-là : SdzBlogBundle.

3. Choisir la destination

Symfony2 vous demande l'endroit où vous voulez que les fichiers du bundle soient générés :

The bundle can be generated anywhere. The suggested default directory uses
the standard conventions.

Target directory [C:/wamp/www/src]:_

Par convention, comme on l'a vu, on place nos bundles dans le répertoire /src. C'est ce que Symfony2 vous propose, appuyez donc sur Entrée.

4. Choisir le format de configuration

Symfony2 vous demande sous quelle forme vous voulez configurer votre bundle. Il s'agit simplement du format de la configuration, que nous ferons plus tard. Il existe plusieurs moyens comme vous pouvez le voir : YAML, XML, PHP ou Annotations.

Target directory [C:/wamp/www/src]:

Determine the format to use for the generated configuration.

Configuration format (yml, xml, php, or annotation) [annotation]:

Chacun a ses avantages et inconvénients. Nous allons utiliser le YAML (yml) ici, car il est bien adapté pour un bundle. Mais sachez que nous utiliserons les annotations pour nos futures entités par exemple. Entrez donc yml.

5. Choisir quelle structure générer

Symfony2 vous demande si vous voulez générer juste le minimum ou une structure plus complète pour le bundle :

Configuration format (yml, xml, php, or annotation) [annotation]: yml

To help you get started faster, the command can generate some
code snippets for you.

Do you want to generate the whole directory structure [no]?

Faisons simple et demandons à Symfony2 de tout nous générer. Entrez donc yes.

6. Confirmez, et c'est joué !

Pour toutes les questions suivantes, confirmez en appuyant sur Entrée à chaque fois. Et voilà, votre bundle est généré :

Do you want to generate the whole directory structure [no]? yes


  Summary before generation


You are going to generate a "Sdz\BlogBundle\SdzBlogBundle" bundle
in "C:/wamp/www/Symfony/src/" using the "yml" format.

Do you confirm generation [yes]?


  Bundle generation


Generating the bundle code: OK
Checking that the bundle is autoloaded: OK
Confirm automatic update of your Kernel [yes]?
Enabling the bundle inside the Kernel: OK
Confirm automatic update of the Routing [yes]?
Importing the bundle routing resource: OK


  You can now start using the generated code!



C:\wamp\www\Symfony>_

Mais pourquoi n'y a-t-il pas la toolbar en bas de la page ?

C'est normal, c'est juste un petit truc à savoir pour éviter de s'arracher les cheveux inutilement. :p La toolbar est un petit bout de code HTML que rajoute Symfony2 à chaque page… contenant la balise </body>. Or sur cette page, vous pouvez afficher la source depuis votre navigateur, il n'y a aucune balise HTML en fait, donc Symfony2 n'ajoute pas la toolbar.

Pour l'activer, rien de plus simple, il nous faut rajouter une toute petite structure HTML. Pour cela, ouvrez le fichier src/Sdz/BlogBundle/Resources/views/Default/index.html.twig, c'est la vue utilisée pour cette page. L'extension .twig signifie qu'on utilise le moteur de templates Twig pour gérer nos vues, on en reparlera bien sûr. Le fichier est plutôt simple, et je vous propose de le changer ainsi :

{# src/Sdz/BlogBundle/Resources/views/Default/index.html.twig #}

<html>
  <body>
    Hello {{ name }}!
  </body>
</html>

Actualisez la page, et voici une magnifique toolbar semblable à la figure suivante qui apparaît en bas de la page ! Seule la balise </body> suffisait, mais quitte à changer autant avoir une structure HTML valide. ;)

La toolbar apparaît
La toolbar apparaît

Que s'est-il passé ?

Dans les coulisses, Symfony2 a fait pas mal de choses, revoyons tout cela à notre rythme.

Symfony2 a généré la structure du bundle

Allez dans le répertoire src/Sdz/BlogBundle, vous pouvez voir tout ce que Symfony2 a généré pour nous. Rappelez-vous la structure d'un bundle que nous avons vu au chapitre précédent : Symfony2 en a généré la plus grande partie !

À savoir : le seul fichier obligatoire pour un bundle est en fait la classe SdzBlogBundle.php à la racine du répertoire. Vous pouvez l'ouvrir et voir ce qu'il contient : pas très intéressant en soi ; heureusement que Symfony l'a généré tout seul. Sachez-le dès maintenant : nous ne modifierons presque jamais ce fichier, vous pouvez passer votre chemin.

Symfony2 a enregistré notre bundle auprès du Kernel

Le bundle est créé, mais il faut dire à Symfony2 de le charger. Pour cela il faut configurer le noyau (le Kernel) pour qu'il le charge. Rappelez-vous, la configuration de l'application se trouve dans le répertoire /app. En l'occurrence, la configuration du noyau se fait dans le fichier app/AppKernel.php :

<?php

use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;

class AppKernel extends Kernel
{
  public function registerBundles()
  {
    $bundles = array(
      new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
      new Symfony\Bundle\SecurityBundle\SecurityBundle(),
      new Symfony\Bundle\TwigBundle\TwigBundle(),
      new Symfony\Bundle\MonologBundle\MonologBundle(),
      new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
      new Symfony\Bundle\AsseticBundle\AsseticBundle(),
      new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
      new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
      new JMS\AopBundle\JMSAopBundle(),
      new JMS\DiExtraBundle\JMSDiExtraBundle($this),
      new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(),
      new Sdz\BlogBundle\SdzBlogBundle(), // Le générateur a rajouté cette ligne
    );

    if (in_array($this->getEnvironment(), array('dev', 'test'))) {
      $bundles[] = new Acme\DemoBundle\AcmeDemoBundle();
      $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
      $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
      $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
    }

    return $bundles;
  }

  // …
}

Cette classe permet donc uniquement de définir quels bundles charger pour l'application. Vous pouvez le voir, ils sont instanciés dans un simple tableau. Les lignes 11 à 22 définissent les bundles à charger pour l'environnement de production. Les lignes 26 à 29 définissent les bundles à charger en plus pour l'environnement de développement.

Comme vous pouvez le voir, le générateur du bundle a modifié lui-même ce fichier pour y ajouter la ligne 22. C'est ce que l'on appelle « enregistrer le bundle dans l'application ».

Vous pouvez voir également qu'il en existe plein d'autres, ce sont tous les bundles par défaut qui apportent des fonctionnalités de base au framework Symfony2. En fait, quand on parle de Symfony2, on parle à la fois de ses composants (Kernel, Routeur, etc.) et de ses bundles.

Symfony2 a enregistré nos routes auprès du Routeur

Les routes ? Le Routeur ?

Pas de panique, nous verrons tout cela dans les prochains chapitres. Sachez juste pour l'instant que le rôle du Routeur, que nous avons brièvement vu sur le schéma du chapitre précédent, est de déterminer quel contrôleur exécuter en fonction de l'URL appelée. Pour cela, il utilise les routes.

Chaque bundle dispose de ses propres routes. Pour notre bundle fraîchement créé, vous pouvez les voir dans le fichier src/Sdz/BlogBundle/Resources/config/routing.yml. En l'occurrence il n'y en a qu'une seule :

# src/Sdz/BlogBundle/Resources/config/routing.yml

SdzBlogBundle_homepage:
    path:     /hello/{name}
    defaults: { _controller: SdzBlogBundle:Default:index }

Or ces routes ne sont pas chargées automatiquement, il faut dire au Routeur « Bonjour, mon bundle SdzBlogBundle contient des routes qu'il faut que tu viennes chercher. » Cela se fait, vous l'aurez deviné, dans la configuration de l'application. Cette configuration se trouve toujours dans le répertoire /app, en l'occurrence pour les routes il s'agit du fichier app/config/routing.yml :

# app/config/routing.yml

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

Ce sont ces lignes qui importent le fichier de routes situé dans notre bundle. Ces lignes ont déjà été générées par le générateur de bundle, vraiment pratique, lui !

À retenir

Ce qu'il faut retenir de tout cela, c'est que pour qu'un bundle soit opérationnel il faut :

Ces trois points sont bien sûr effectués automatiquement lorsqu'on utilise le générateur. Mais vous pouvez tout à fait créer un bundle sans l'utiliser, et il faudra alors remplir cette petite checklist.

Par la suite, tout notre code source sera situé dans des bundles. Un moyen très propre de bien structurer son application.

En résumé

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Utilisation de la console Mon premier « Hello World ! » avec Symfony2

Mon premier « Hello World ! » avec Symfony2

Créons notre bundle Créons notre route

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

Mon premier « Hello World ! » avec Symfony2 Créons notre contrôleur

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
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à :

# 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 !

Essayons de comprendre rapidement cette route :

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 (« : ») :

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 :

# 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 :

# 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.

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 !

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Mon premier « Hello World ! » avec Symfony2 Créons notre contrôleur

Créons notre contrôleur

Créons notre route Créons notre template Twig

Le rôle du contrôleur

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

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 :

<?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 !
Le Hello World s'affiche !

Maintenant, essayons de comprendre rapidement ce fichier :

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 !

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Créons notre route Créons notre template Twig

Créons notre template Twig

Créons notre contrôleur Notre objectif : créer un blog

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).

<!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>
<!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 :

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

{# 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 :

<?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 !
Notre vue Hello World s'affiche bien !

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() :

<?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 :

<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.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Créons notre contrôleur Notre objectif : créer un blog

Notre objectif : créer un blog

Créons notre template Twig Le routeur de Symfony2

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 :

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 :

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 :

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 :

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

É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. :)

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é

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Créons notre template Twig Le routeur de Symfony2

Le routeur de Symfony2

Notre objectif : créer un blog Le fonctionnement

Comme nous avons pu le voir, le rôle du routeur est, à partir d'une URL, de déterminer quel contrôleur appeler et avec quels arguments. Cela permet de configurer son application pour avoir de très belles URL, ce qui est important pour le référencement et même pour le confort des visiteurs. Soyons d'accord, l'URL /article/le-systeme-de-route est bien plus sexy que index.php?controlleur=article&methode=voir&id=5 !

Vous avez sans doute déjà entendu parler d'URL Rewriting ? Le routeur, bien que différent, permet effectivement de faire l'équivalent de l'URL Rewriting, mais il le fait côté PHP, et donc est bien mieux intégré à notre code.

Le fonctionnement

Le routeur de Symfony2 Les routes de base

L'objectif de ce chapitre est de vous transmettre toutes les connaissances pour pouvoir créer ce que l'on appelle un fichier de mapping des routes (un fichier de correspondances, en français). Ce fichier, généralement situé dans votreBundle/Resources/config/routing.yml, contient la définition des routes. Chaque route fait la correspondance entre une URL et le contrôleur à appeler. Je vous invite à mettre dès maintenant les routes présentées au code suivant dans le fichier, nous allons travailler dessus dans ce chapitre :

# src/sdz/BlogBundle/Resources/config/routing.yml

sdzblog_accueil:
    path:      /blog
    defaults:  { _controller: SdzBlogBundle:Blog:index }
    
sdzblog_voir:
    path:      /blog/article/{id}
    defaults:  { _controller: SdzBlogBundle:Blog:voir }
    
sdzblog_ajouter:
    path:      /blog/ajouter
    defaults:  { _controller: SdzBlogBundle:Blog:ajouter }

Fonctionnement du routeur

Dans le code précédent, vous pouvez distinguer trois blocs. Chacun correspond à une route. Nous les verrons en détail plus loin, mais vous pouvez constater que chaque route prend :

Le but du routeur est donc, à partir d'une URL, de trouver la route correspondante et de retourner le contrôleur que veut cette route. Pour trouver la bonne route, le routeur va les parcourir une par une, dans l'ordre du fichier, et s'arrêter à la première route qui fonctionne. La figure suivante est un schéma équivalent au chapitre précédent, mais actualisé pour notre fichier de routes précédent.

Cheminement du routeur
Cheminement du routeur

Et voici en texte le fonctionnement, pas à pas :

  1. On appelle l'URL /blog/article/5.

  2. Le routeur essaie de faire correspondre cette URL avec le path de la première route. Ici, /blog/article/5 ne correspond pas du tout à /blog (ligne path de la première route).

  3. Le routeur passe donc à la route suivante. Il essaie de faire correspondre /blog/article/5 avec /blog/article/{id}. Nous le verrons plus loin, mais {id} est un paramètre, une sorte de joker « je prends tout ». Cette route correspond, car nous avons bien :

    • /blog/article (URL) = /blog/article (route) ;

    • 5 (URL) = {id} (route).

  4. Le routeur s'arrête donc, il a trouvé sa route.

  5. Il demande à la route : « Quel contrôleur souhaites-tu appeler, et avec quels paramètres ? », la route répond : « Je veux le contrôleur SdzBlogBundle:Blog:voir, avec le paramètre $id = 5. »

  6. Le routeur renvoie donc ces informations au Kernel (le noyau de Symfony2).

  7. Le noyau va exécuter le bon contrôleur !

Dans le cas où le routeur ne trouve aucune route correspondante, le noyau de Symfony2 va déclencher une erreur 404.

Pour chaque page, il est possible de visualiser toutes les routes que le routeur essaie une à une, et celle qu'il utilise finalement. C'est le Profiler qui s'occupe de tracer cela, accessible depuis la barre d'outils : cliquez sur le nom de la route dans la barre d'outils, « sdzblog_accueil » si vous êtes sur la page /blog. Ce lien vous amène dans l'onglet « Request » du Profiler, mais allez dans l'onglet « Routing » qui nous intéresse. Vous devriez obtenir la figure suivante.

Liste des routes enregistrées par le routeur
Liste des routes enregistrées par le routeur

Convention pour le nom du contrôleur

Vous l'avez vu, lorsque l'on définit le contrôleur à appeler dans la route, il y a une convention à respecter : la même que pour appeler un template (nous l'avons vue au chapitre précédent). Un rappel ne fait pas de mal : lorsque vous écrivez « SdzBlogBundle:Blog:voir », vous avez trois informations :

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Le routeur de Symfony2 Les routes de base

Les routes de base

Le fonctionnement Les routes avancées

Créer une route

Étudions la première route plus en détail :

# src/Sdz/BlogBundle/config/Resources/routing.yml

sdzblog_accueil:
    path:      /blog
    defaults:  { _controller: SdzBlogBundle:Blog:index }

Ce bloc représente ce que l'on nomme une « route ». Elle est constituée au minimum de trois éléments :

Vous avez maintenant les bases pour créer une route simple !

Créer une route avec des paramètres

Reprenons la deuxième route de notre exemple :

# src/Sdz/BlogBundle/Resources/config/routing.yml

sdzblog_voir:
    path:     /blog/article/{id}
    defaults: { _controller: SdzBlogBundle:Blog:voir }

Grâce au paramètre {id} dans le path de notre route, toutes les URL du type /blog/article/* seront gérées par cette route, par exemple : /blog/article/5 ou /blog/article/654, ou même /blog/article/sodfihsodfih (on n'a pas encore dit que {id} devait être un nombre, patience !). Par contre, l'URL /blog/article ne sera pas interceptée, car le paramètre {id} n'est pas renseigné. En effet, les paramètres sont par défaut obligatoires, nous verrons quand et comment les rendre facultatifs plus loin dans ce chapitre.

Mais si le routeur s’arrêtait là, il n'aurait aucun intérêt. Toute sa puissance réside dans le fait que ce paramètre {id} est accessible depuis votre contrôleur ! Si vous appelez l'URL /blog/article/5, alors depuis votre contrôleur vous aurez la variable $id (du nom du paramètre) qui aura pour valeur « 5 ». Je vous invite à créer la méthode correspondante dans le contrôleur :

<?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
{
  // … ici la méthode indexAction() que l'on a déjà créée

  // La route fait appel à SdzBlogBundle:Blog:voir, on doit donc définir la méthode voirAction
  // On donne à cette méthode l'argument $id, pour correspondre au paramètre {id} de la route
  public function voirAction($id)
  {
    // $id vaut 5 si l'on a appelé l'URL /blog/article/5
        
    // Ici, on récupèrera depuis la base de données l'article correspondant à l'id $id
    // Puis on passera l'article à la vue pour qu'elle puisse l'afficher

    return new Response("Affichage de l'article d'id : ".$id.".");
  }
}

N'oubliez pas de tester votre code à l'adresse suivante : http://localhost/Symfony/web/app_dev.php/blog/article/5, et amusez-vous à changer la valeur du paramètre.

Vous pouvez bien sûr multiplier les paramètres au sein d'une même route. Ajoutez cette route juste après la route sdzblog_voir, pour l'exemple :

# src/Sdz/BlogBundle/Resources/config/routing.yml

sdzblog_voir_slug:
    path:     /blog/{annee}/{slug}.{format}
    defaults: { _controller: SdzBlogBundle:Blog:voirSlug }

Cette route permet d'intercepter les URL suivantes : /blog/2011/mon-weekend.html ou /blog/2012/symfony.xml, etc. Et voici la méthode correspondante qu'on aurait côté contrôleur :

<?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
{
  // … ici les méthodes indexAction() et voirAction() que l'on a déjà créées

  // On récupère tous les paramètres en arguments de la méthode
  public function voirSlugAction($slug, $annee, $format)
  {
    // Ici le contenu de la méthode
    return new Response("On pourrait afficher l'article correspondant au slug '".$slug."', créé en ".$annee." et au format ".$format.".");
  }
}

Revenez à notre route et notez également le point entre les paramètres {slug} et {format} : vous pouvez en effet séparer vos paramètres soit avec le slash (« / »), soit avec le point (« . »). Veillez donc à ne pas utiliser de point dans le contenu de vos paramètres. Par exemple, pour notre paramètre {slug}, une URL /blog/2011/mon-weekend.etait.bien.html ne va pas correspondre à cette route, car :

La route attend des paramètres à mettre en face de ces dernières valeurs, et comme il n'y en a pas cette route dit : « Cette URL ne me correspond pas, passez à la route suivante. » Attention donc à ce petit détail. ;)

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Le fonctionnement Les routes avancées

Les routes avancées

Les routes de base Générer des URL

Créer une route avec des paramètres et leurs contraintes

Nous avons créé une route avec des paramètres, très bien. Mais si quelqu'un essaie d'atteindre l'URL /blog/oaisd/aouish.oasidh, eh bien, rien ne l'en empêche ! Et pourtant, « oaisd » n'est pas tellement une année valide ! La solution ? Les contraintes sur les paramètres. Reprenons notre dernière route sdzblog_voir_slug :

# src/Sdz/BlogBundle/Resources/config/routing.yml

sdzblog_voir_slug:
    path:      /blog/{annee}/{slug}.{format}
    defaults:  { _controller: SdzBlogBundle:Blog:voirSlug }

Nous voulons ne récupérer que les bonnes URL où l'année vaut « 2010 » et non « oshidf », par exemple. Cette dernière devrait retourner une erreur 404 (page introuvable). Pour cela, il nous suffit qu'aucune route ne l'intercepte ; ainsi, le routeur arrivera à la fin du fichier sans aucune route correspondante et il déclenchera tout seul une erreur 404.

Comment faire pour que notre paramètre {annee} n'intercepte pas « oshidf » ? C'est très simple :

# src/Sdz/BlogBundle/Resources/config/routing.yml

sdzblog_voir_slug:
    path:      /blog/{annee}/{slug}.{format}
    defaults:  { _controller: SdzBlogBundle:Blog:voirSlug }
    requirements:
        annee:  \d{4}
        format: html|xml

Nous avons ajouté la section requirements. Comme vous pouvez le voir, on utilise les expressions régulières pour déterminer les contraintes que doivent respecter les paramètres. Ici :

Maintenant, nous souhaitons aller plus loin. En effet, si le « .xml » est utile pour récupérer l'article au format XML (pourquoi pas ?), le « .html » semble inutile : par défaut, le visiteur veut toujours du HTML. Il faut donc rendre le paramètre {format} facultatif.

Utiliser des paramètres facultatifs

Reprenons notre route et ajoutons-y la possibilité pour {format} de ne pas être renseigné :

# src/Sdz/BlogBundle/Resources/config/routing.yml

sdzblog_voir_slug:
    path:      /blog/{annee}/{slug}.{format}
    defaults:  { _controller: SdzBlogBundle:Blog:voirSlug, format: html }
    requirements:
        annee:  \d{4}
        format: html|xml

Nous avons juste ajouté une valeur par défaut dans le tableau defaults : format: html. C'est aussi simple que cela !
Ainsi, l'URL /blog/2011/mon-weekend sera bien interceptée et le paramètre format sera mis à sa valeur par défaut, à savoir « html ». Au niveau du contrôleur, rien ne change : vous gardez l'argument $format comme avant et celui-ci vaudra « html », la valeur par défaut.

Utiliser des « paramètres système »

Prenons l'exemple de notre paramètre {format} : lorsqu'il vaut « xml », vous allez afficher du XML et devrez donc envoyer le header avec le bon Content-type. Les développeurs de Symfony2 ont pensé à nous et prévu des « paramètres système ». Ils s'utilisent exactement comme des paramètres classiques, mais effectuent automatiquement des actions supplémentaires :

Ajouter un préfixe lors de l'import de nos routes

Vous avez remarqué que nous avons mis /blog au début du path de chacune de nos routes. En effet, on crée un blog, on aimerait donc que toutes les URL aient ce préfixe /blog. Au lieu de les répéter à chaque fois, Symfony2 vous propose de rajouter un préfixe lors de l'import du fichier de notre bundle.

Modifiez donc le fichier app/config/routing.yml comme suit :

# app/config/routing.yml

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

Vous pouvez ainsi enlever la partie /blog de chacune de vos routes. Bonus : si un jour vous souhaitez changer /blog par /blogdemichel, vous n'aurez qu'à modifier une seule ligne. ;)

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Les routes de base Générer des URL

Générer des URL

Les routes avancées Application : les routes de notre blog

Pourquoi générer des URL ?

J'ai mentionné précédemment que le routeur pouvait aussi générer des URL à partir du nom des routes. En effet, vu que le routeur a toutes les routes à sa disposition, il est capable d'associer une route à une certaine URL, mais également de reconstruire l'URL correspondant à une certaine route. Ce n'est pas une fonctionnalité annexe, mais bien un outil puissant que nous avons là !

Par exemple, nous avons une route nommée « sdzblog_voir » qui écoute l'URL /blog/article/{id}. Vous décidez un jour de raccourcir vos URL et vous aimeriez bien que vos articles soient disponibles depuis /blog/a/{id}. Si vous aviez écrit toutes vos URL à la main, vous auriez dû toutes les changer à la main, une par une. Grâce à la génération d'URL, vous ne modifiez que la route : ainsi, toutes les URL générées seront mises à jour ! C'est un exemple simple, mais vous pouvez trouver des cas bien réels et tout aussi gênants sans la génération d'URL.

Comment générer des URL ?

1. Depuis le contrôleur

Pour générer une URL, vous devez le demander au routeur en lui donnant deux arguments : le nom de la route ainsi que les éventuels paramètres de cette route.

Depuis un contrôleur, c'est la méthode <?php $this->generateUrl() qu'il faut appeler. Par exemple :

<?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()
  {
    // On fixe un id au hasard ici, il sera dynamique par la suite, évidemment
    $id = 5;

    // On veut avoir l'URL de l'article d'id $id.
    $url = $this->generateUrl('sdzblog_voir', array('id' => $id));
    // $url vaut « /blog/article/5 »

    // On redirige vers cette URL (ça ne sert à rien, on est d'accord, c'est pour l'exemple !)
    return $this->redirect($url);
  }
}

Pour générer une URL absolue, lorsque vous l'envoyez par e-mail, par exemple, il faut mettre le troisième argument à true. Exemple :

<?php
$url = $this->generateUrl('sdzblog_voir', array('id' => $id), true);

Ainsi, $url vaut http://monsite.com/blog/article/5 et pas uniquement /blog/article/5.

2. Depuis une vue Twig

Vous aurez bien plus l'occasion de devoir générer une URL depuis la vue. C'est la fonction path qu'il faut utiliser depuis un template Twig :

{# Dans une vue Twig, en considérant bien sûr que la variable article_id est disponible #}

<a href="{{ path('sdzblog_voir', { 'id': article_id }) }}">Lien vers l'article d'id {{ article_id }}</a>

Et pour générer une URL absolue depuis Twig, pas de troisième argument, mais on utilise la fonction url() au lieu de path(). Elle s'utilise exactement de la même manière, seul le nom change.

Voilà : vous savez générer des URL, ce n'était vraiment pas compliqué. Pensez bien à utiliser la fonction {{ path }} pour tous vos liens dans vos templates. :)

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Les routes avancées Application : les routes de notre blog

Application : les routes de notre blog

Générer des URL Les contrôleurs avec Symfony2

Construction des routes

Revenons à notre blog. Maintenant que nous savons créer des routes, je vous propose de faire un premier jet de ce que seront nos URL. Voici les routes que je vous propose de créer, libre à vous d'en changer.

Page d'accueil

On souhaite avoir une URL très simple pour la page d'accueil : /blog. Comme /blog est défini comme préfixe lors du chargement des routes de notre bundle, le path ici est « / ». Mais on veut aussi pouvoir parcourir les articles plus anciens, donc il nous faut une notion de page courante. En ajoutant le paramètre facultatif {page}, nous aurons :

/blog

page = 1

/blog/1

page = 1

/blog/2

page = 2

C'est plutôt joli, non ? Voici la route :

# src/Sdz/BlogBundle/Resources/config/routing.yml

sdzblog_accueil:
    path:      /{page}
    defaults:  { _controller: SdzBlogBundle:Blog:index, page: 1 }
    requirements:
        page:  \d*

Page de visualisation d'un article

Pour la page d'un unique article, la route est très simple. Il suffit juste de bien mettre un paramètre {id} qui nous servira à récupérer le bon article côté contrôleur. Voici la route :

# src/Sdz/BlogBundle/Resources/config/routing.yml

sdzblog_voir:
    path:      /article/{id}
    defaults:  { _controller: SdzBlogBundle:Blog:voir }

Ajout, modification et suppression

Les routes sont simples :

# src/Sdz/BlogBundle/Resources/config/routing.yml

sdzblog_ajouter:
    path:      /ajouter
    defaults:  { _controller: SdzBlogBundle:Blog:ajouter }

sdzblog_modifier:
    path:      /modifier/{id}
    defaults:  { _controller: SdzBlogBundle:Blog:modifier }
    requirements:
        id:  \d+

sdzblog_supprimer:
    path:      /supprimer/{id}
    defaults:  { _controller: SdzBlogBundle:Blog:supprimer }
    requirements:
        id:  \d+

Récapitulatif

Voici le code complet de notre fichier src/Sdz/BlogBundle/Resources/config/routing.yml :

# src/Sdz/BlogBundle/Resources/config/routing.yml

sdzblog_accueil:
    path:      /{page}
    defaults:  { _controller: SdzBlogBundle:Blog:index, page: 1 }
    requirements:
        page:  \d*

sdzblog_voir:
    path:      /article/{id}
    defaults:  { _controller: SdzBlogBundle:Blog:voir }
    requirements:
        id:  \d+

sdzblog_ajouter:
    path:      /ajouter
    defaults:  { _controller: SdzBlogBundle:Blog:ajouter }

sdzblog_modifier:
    path:      /modifier/{id}
    defaults:  { _controller: SdzBlogBundle:Blog:modifier }
    requirements:
        id:  \d+

sdzblog_supprimer:
    path:      /supprimer/{id}
    defaults:  { _controller: SdzBlogBundle:Blog:supprimer }
    requirements:
        id:  \d+

N'oubliez pas de bien ajouter le préfixe /blog lors de l'import de ce fichier, dans app/config/routing.yml :

# app/config/routing.yml

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

Pour conclure

Ce chapitre est terminé, et vous savez maintenant tout ce qu'il faut savoir sur le routeur et les routes.

Retenez que ce système de routes vous permet premièrement d'avoir des belles URL, et deuxièmement de découpler le nom de vos URL du nom de vos contrôleurs. Ajoutez à cela la génération d'URL, et vous avez un système extrêmement flexible et maintenable.

Le tout sans trop d'efforts !

Pour plus d'informations sur le système de routes, n'hésitez pas à lire la documentation officielle.

En résumé

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Générer des URL Les contrôleurs avec Symfony2

Les contrôleurs avec Symfony2

Application : les routes de notre blog Le rôle du contrôleur

Ah, le contrôleur ! Vous le savez, c'est lui qui contient toute la logique de notre site internet. Cependant, cela ne veut pas dire qu'il contient beaucoup de code. En fait, il ne fait qu'utiliser des services, les modèles et appeler la vue. Finalement, c'est un chef d'orchestre qui se contente de faire la liaison entre tout le monde.

Nous verrons dans ce chapitre ses droits, mais aussi son devoir ultime : retourner une réponse !

Le rôle du contrôleur

Les contrôleurs avec Symfony2 Manipuler l'objet Request

Retourner une réponse

Je vous l'ai dit de nombreuses fois depuis le début de ce cours : le rôle du contrôleur est de retourner une réponse.

Mais concrètement, qu'est-ce que cela signifie, « retourner une réponse » ?

Souvenez-vous, Symfony2 s'est inspiré des concepts du protocole HTTP. Il existe dans Symfony2 une classe Response. Retourner une réponse signifie donc tout simplement : instancier un objet Response, disons $response, et faire un return $response.

Voici le contrôleur le plus simple qui soit, c'est le contrôleur qu'on avait à créer dans un des chapitres précédents. Il dispose d'une seule méthode, nommée « index », et retourne une réponse qui ne contient que « Hello World ! » :

<?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 !");
  }
}

Et voilà, votre contrôleur remplit parfaitement son rôle !

Bien sûr, vous n'irez pas très loin en sachant juste cela. C'est pourquoi la suite de ce chapitre est découpée en deux parties :

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Les contrôleurs avec Symfony2 Manipuler l'objet Request

Manipuler l'objet Request

Le rôle du contrôleur Manipuler l'objet Response

Les paramètres de la requête

Heureusement, toutes les requêtes que l'on peut faire sur un site internet ne sont pas aussi simples que notre « Hello World ! ». Dans bien des cas, une requête contient des paramètres : l'id d'un article à afficher, le nom d'un membre à chercher dans la base de données, etc. Les paramètres sont la base de toute requête : la construction de la page à afficher dépend de chacun des paramètres en entrée.

Ces paramètres, nous savons déjà les gérer, nous l'avons vu dans le chapitre sur le routeur. Mais voici un petit rappel.

Les paramètres contenus dans les routes

Tout d'abord côté route, souvenez-vous, on utilisait déjà des paramètres. Prenons l'exemple de la route sdzblog_voir :

# src/Sdz/BlogBundle/Resources/config/routing.yml

sdzblog_voir:
    path:      /article/{id}
    defaults:  { _controller: SdzBlogBundle:Blog:voir }
    requirements:
        id:  \d+

Ici, le paramètre {id} de la requête est récupéré par la route, qui va le transformer en argument $id pour le contrôleur. On a déjà fait la méthode correspondante dans le contrôleur, la voici pour rappel :

<?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 voirAction($id)
  {
    return new Response("Affichage de l'article d'id : ".$id.".");
  }
}

Voici donc la première manière de récupérer des arguments : ceux contenus dans la route.

Les paramètres hors routes

En plus des paramètres de routes que nous venons de voir, vous pouvez récupérer les autres paramètres de l'URL, disons, « à l'ancienne ». Prenons par exemple l'URL /blog/article/5?tag=vacances, il nous faut bien un moyen pour récupérer ce paramètre tag ! C'est ici qu'intervient l'objet Request. Tout d'abord, voici comment récupérer la requête depuis un contrôleur :

<?php
$request = $this->getRequest();

Voilà, c'est aussi simple que cela ! Maintenant que nous avons notre requête, récupérons nos paramètres :

<?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 voirAction($id)
  {
    // On récupère la requête
    $request = $this->getRequest();

    // On récupère notre paramètre tag
    $tag = $request->query->get('tag');

    return new Response("Affichage de l'article d'id : ".$id.", avec le tag : ".$tag);
  }
}

Et vous n'avez plus qu'à tester le résultat : /blog/article/9?tag=vacances.

Nous avons utilisé <?php $request->query pour récupérer les paramètres de l'URL passés en GET, mais vous savez qu'il existe d'autres types de paramètres :

Type de paramètres

Méthode Symfony2

Méthode traditionnelle

Exemple

Variables d'URL

<?php $request->query

$_GET

<?php $request->query->get('tag')

Variables de formulaire

<?php $request->request

$_POST

<?php $request->request->get('tag')

Variables de cookie

<?php $request->cookies

$_COOKIE

<?php $request->cookies->get('tag')

Variables de serveur

<?php $request->server

$_SERVER

<?php $request->server->get('REQUEST_URI')

Variables d'entête

<?php $request->headers

$_SERVER['HTTP_*']

<?php $request->headers->get('USER_AGENT')

Paramètres de route

<?php $request->attributes

n/a

<?php $request->attributes->get('id')
Est équivalent à :
<?php $id

Avec cette façon d'accéder aux paramètres, vous n'avez pas besoin de tester leur existence. Par exemple, si vous faites $request->query->get('sdf') alors que le paramètre sdf n'est pas défini dans l'URL, cela vous retournera une chaîne vide, et non une erreur.

Les autres méthodes de l'objet Request

Heureusement, l'objet Request ne se limite pas à la récupération de paramètres. Il permet de savoir plusieurs choses intéressantes à propos de la requête en cours, voyons ses possibilités.

Récupérer la méthode de la requête HTTP

Pour savoir si la page a été récupérée via GET (clic sur un lien) ou via POST (envoi d'un formulaire), il existe la méthode <?php $request->getMethod() ?> :

<?php
if( $request->getMethod() == 'POST' )
{
  // Un formulaire a été envoyé, on peut le traiter ici
}

Savoir si la requête est une requête AJAX

Lorsque vous utiliserez AJAX dans votre site, vous aurez sans doute besoin de savoir, depuis le contrôleur, si la requête en cours est une requête AJAX ou non. Par exemple, pour renvoyer du XML ou du JSON à la place du HTML. Pour cela, rien de plus simple !

<?php
if( $request->isXmlHttpRequest() )
{
  // C'est une requête AJAX, retournons du JSON, par exemple
}

Toutes les autres

Pour avoir la liste exhaustive des méthodes disponibles sur l'objet Request, je vous invite à lire l'API de cet objet sur le site de Symfony2. Vous y trouverez toutes les méthodes, même si nous avons déjà survolé les principales.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Le rôle du contrôleur Manipuler l'objet Response

Manipuler l'objet Response

Manipuler l'objet Request Les différents services

Décomposition de la construction d'un objet Response

Pour que vous compreniez ce qu'il se passe en coulisses lors de la création d'une réponse, voyons la manière longue et décomposée de construire et de retourner une réponse. Pour l'exemple, traitons le cas d'une page d'erreur 404 (page introuvable) :

<?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
{
  // On modifie voirAction, car elle existe déjà
  public function voirAction($id)
  {
    // On crée la réponse sans lui donner de contenu pour le moment
    $response = new Response;

    // On définit le contenu
    $response->setContent('Ceci est une page d\'erreur 404');

    // On définit le code HTTP
    // Rappelez-vous, 404 correspond à « page introuvable »
    $response->setStatusCode(404);

    // On retourne la réponse
    return $response;
  }
}

N'hésitez pas à tester cette page, l'URL est http://localhost/Symfony/web/app_dev.php/blog/article/5 si vous avez gardé les mêmes routes depuis le début.

Je ne vous le cache pas : nous n'utiliserons jamais cette longue méthode ! Lisez plutôt la suite.

Réponses et vues

Généralement, vous préférerez que votre réponse soit contenue dans une vue, dans un template. Heureusement pour nous, le contrôleur dispose d'un raccourci : la méthode <?php $this->render(). Elle prend en paramètres le nom du template et ses variables, puis s'occupe de tout : créer la réponse, y passer le contenu du template, et retourner la réponse. La voici en action :

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

namespace Sdz\BlogBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

// Nous n'avons plus besoin du use pour l'objet Response
// use Symfony\Component\HttpFoundation\Response;

class BlogController extends Controller
{
  public function voirAction($id)
  {
    // On utilise le raccourci : il crée un objet Response
    // Et lui donne comme contenu le contenu du template
    return $this->render('SdzBlogBundle:Blog:voir.html.twig', array(
      'id'  => $id,
    ));
  }
}

Et voilà, en une seule ligne, c'est bouclé ! C'est comme cela que nous générerons la plupart de nos réponses. Finalement, l'objet Response est utilisé en coulisses, nous n'avons pas à le manipuler directement.

N'oubliez pas de créer la vue associée bien entendu :

{# src/Sdz/BlogBundle/Resources/view/Blog/voir.html.twig #}

<!DOCTYPE html>
<html>
  <head>
    <title>Lecture de l'article {{ id }}</title>
  </head>
  <body>
    <h1>Hello Article n°{{ id }} !</h1>
  </body>
</html>

Si vous ne deviez retenir qu'une seule chose de cette section, c'est bien cette méthode <?php $this->render(), car c'est vraiment ce que nous utiliserons en permanence. ;)

Réponse et redirection

Vous serez sûrement amenés à faire une redirection vers une autre page. Or notre contrôleur est obligé de retourner une réponse. Comment gérer une redirection ? Eh bien, vous avez peut-être évité le piège, mais une redirection est une réponse HTTP. Pour faire cela, il existe également un raccourci du contrôleur : la méthode <?php $this->redirect(). Elle prend en paramètre l'URL vers laquelle vous souhaitez faire la redirection et s'occupe de créer une réponse, puis d'y définir un header qui contiendra votre URL. En action, cela donne le code suivant :

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

namespace Sdz\BlogBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BlogController extends Controller
{
  public function voirAction($id)
  {
    // On utilise la méthode « generateUrl() » pour obtenir l'URL de la liste des articles à la page 2
    // Par exemple
    return $this->redirect( $this->generateUrl('sdzblog_accueil', array('page' => 2)) );
  }
}

Essayez d'aller à l'adresse http://localhost/Symfony/web/app_dev.php/blog/article/5 et vous serez redirigés vers l'accueil !

Changer le Content-type de la réponse

Lorsque vous retournez autre chose que du HTML, il faut que vous changiez le Content-type de la réponse. Ce Content-type permet au navigateur qui recevra votre réponse de savoir à quoi s'attendre dans le contenu. Prenons l'exemple suivant : vous recevez une requête AJAX et souhaitez retourner un tableau en JSON :

<?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 voirAction($id)
  {
    // Créons nous-mêmes la réponse en JSON, grâce à la fonction json_encode()
    $response = new Response(json_encode(array('id' => $id)));

    // Ici, nous définissons le Content-type pour dire que l'on renvoie du JSON et non du HTML
    $response->headers->set('Content-Type', 'application/json');

    return $response;

    // Nous n'avons pas utilisé notre template ici, car il n'y en a pas vraiment besoin
  }
}

Testez le rendu en allant sur http://localhost/Symfony/web/app_dev.php/blog/article/5.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Manipuler l'objet Request Les différents services

Les différents services

Manipuler l'objet Response Application : le contrôleur de notre blog

Qu'est-ce qu'un service ?

Je vous en ai déjà brièvement parlé : un service est un script qui remplit un rôle précis et que l'on peut utiliser depuis notre contrôleur.

Imaginez par exemple un service qui a pour but d'envoyer des e-mails. Depuis notre contrôleur, on appelle ce service, on lui donne les informations nécessaires (contenu de l'e-mail, destinataire, etc.), puis on lui dit d'envoyer l'e-mail. Ainsi, toute la logique « création et envoi d'e-mail » se trouve dans ce service et non dans notre contrôleur. Cela nous permet de réutiliser ce service très facilement ! En effet, si vous codez en dur l'envoi d'e-mail dans un contrôleur A et que, plus tard, vous avez envie d'envoyer un autre e-mail depuis un contrôleur B, comment réutiliser ce que vous avez déjà fait ? C'est impossible et c'est exactement pour cela que les services existent.

Accéder aux services

Pour accéder aux services depuis votre contrôleur, il faut utiliser la méthode <?php $this->get() du contrôleur. Par exemple, le service pour envoyer des e-mails se nomme justement « mailer ». Pour employer ce service, nous faisons donc :

<?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 voirAction($id)
  {
    // Récupération du service
    $mailer = $this->get('mailer');

    // Création de l'e-mail : le service mailer utilise SwiftMailer, donc nous créons une instance de Swift_Message
    $message = \Swift_Message::newInstance()
      ->setSubject('Hello zéro !')
      ->setFrom('[email protected]')
      ->setTo('[email protected]')
      ->setBody('Coucou, voici un email que vous venez de recevoir !');

    // Retour au service mailer, nous utilisons sa méthode « send() » pour envoyer notre $message
    $mailer->send($message);

    // N'oublions pas de retourner une réponse, par exemple une page qui afficherait « L'e-mail a bien été envoyé »
    return new Response('Email bien envoyé');
  }
}

Pour que l'envoi d'e-mail fonctionne, n'oubliez pas de configurer vos paramètres si ce n'est pas déjà fait. Ouvrez le fichier app/config/parameters.yml pour modifier les paramètres mailer_*. Si vous voulez utiliser votre compte Gmail :

# app/config/parameters.yml

    mailer_transport:  gmail
    mailer_host:       
    mailer_user:       [email protected]
    mailer_password:   mdp

Et si vous voulez utiliser un serveur SMTP classique :

# app/config/parameters.yml

    mailer_transport:  smtp
    mailer_host:       smtp.votre-serveur.fr
    mailer_user:       identifiant
    mailer_password:   mdp

Chargez la page /blog/article/5, et allez lire votre e-mail !

Brève liste des services

Maintenant que vous savez récupérer des services, encore faut-il connaître leur nom ! Et savoir les utiliser ! Ci-après est dressée une courte liste de quelques services utiles.

Templating

Templating est un service qui vous permet de gérer vos templates (vos vues, vous l'aurez compris). En fait, vous avez déjà utilisé ce service… via le raccourci <?php $this->render ! Voici la version longue d'un <?php $this->render('MonTemplate') :

<?php
// …
public function voirAction($id)
{
  // Récupération du service
  $templating = $this->get('templating');

  // On récupère le contenu de notre template
  $contenu = $templating->render('SdzBlogBundle:Blog:voir.html.twig');

  // On crée une réponse avec ce contenu et on la retourne
  return new Response($contenu);
}

Le service Templating est utile, par exemple, pour notre e-mail de tout à l'heure. Nous avons écrit le contenu de l'e-mail en dur, ce qui n'est pas bien, évidemment. Nous devrions avoir un template pour cela. Et pour en récupérer le contenu, nous utilisons <?php $templating->render(). ;)

Une autre fonction de ce service qui peut servir, c'est <?php $templating->exists('SdzBlogBundle:Blog:inexistant') qui permet de vérifier si « SdzBlogBundle:Blog:inexistant » existe ou non.

Request

Eh oui, encore elle. C'est également un service ! Tout à l'heure on l'a récupéré via $this->getRequest(), mais on aurait également pu la récupérer de cette façon : <?php $this->get('request'). En fait, la première méthode n'est qu'un raccourci vers la deuxième.

Session

Les outils de session sont également intégrés dans un service. Vous pouvez le récupérer via <?php $this->get('session'). Pour définir et récupérer des variables en session, il faut utiliser les méthodes get() et set(), tout simplement :

<?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 voirAction($id)
  {
    // Récupération du service
    $session = $this->get('session');
    
    // On récupère le contenu de la variable user_id
    $user_id = $session->get('user_id');

    // On définit une nouvelle valeur pour cette variable user_id
    $session->set('user_id', 91);

    // On n'oublie pas de renvoyer une réponse
    return new Response('Désolé je suis une page de test, je n\'ai rien à dire');
  }
}

La session se lance automatiquement dès que vous vous en servez. Voyez par exemple à la figure suivante ce que le Profiler me dit sur une page où je n'utilise pas la session.

On constate qu'il n'y a pas d'attribut dans la session
On constate qu'il n'y a pas d'attribut dans la session

Et voici le Profiler après que nous avons défini la variable user_id en session, à la figure suivante.

Ici, on constate que l'attribut user_id est bien défini, avec comme valeur 91
Ici, on constate que l'attribut user_id est bien défini, avec comme valeur 91

Le Profiler nous donne même les informations sur la date de création de la session, etc.

Un autre outil très pratique du service de session est ce que l'on appelle les « messages flash ». Un terme précis pour désigner en réalité une variable de session qui ne dure que le temps d'une seule page. C'est une astuce utilisée pour les formulaires par exemple : la page qui traite le formulaire définit un message flash (« Article bien enregistré » par exemple) puis redirige vers la page de visualisation de l'article nouvellement créé. Sur cette page, le message flash s'affiche, et est détruit de la session. Alors si l'on change de page ou qu'on l'actualise, le message flash ne sera plus présent. Voici un exemple d'utilisation (dans la méthode ajouterAction()) :

<?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 voirAction($id)
  {
    return $this->render('SdzBlogBundle:Blog:voir.html.twig', array(
      'id'  => $id
    ));
  }
    
  // Ajoutez cette méthode ajouterAction :
  public function ajouterAction()
  {
    // Bien sûr, cette méthode devra réellement ajouter l'article
    // Mais faisons comme si c'était le cas
    $this->get('session')->getFlashBag()->add('info', 'Article bien enregistré');

    // Le « flashBag » est ce qui contient les messages flash dans la session
    // Il peut bien sûr contenir plusieurs messages :
    $this->get('session')->getFlashBag()->add('info', 'Oui oui, il est bien enregistré !');
        
    // Puis on redirige vers la page de visualisation de cet article
    return $this->redirect( $this->generateUrl('sdzblog_voir', array('id' => 5)) );
  }
}

Vous pouvez voir que la méthode ajouterAction définit deux messages flash (appelés ici « info »). La lecture de ces messages se fait dans la vue de l'action voirAction, que j'ai modifiée comme ceci :

{# src/Sdz/BlogBundle/Resources/views/Blog/voir.html.twig #}

<!DOCTYPE html>
<html>
  <head>
    <title>Bienvenue sur ma première page avec le Site du Zéro !</title>
  </head>
  <body>
    <h1>Lecture d'un article</h1>

    <p>
      {# On affiche tous les messages flash dont le nom est « info » #}
      {% for message in app.session.flashbag.get('info') %}
        <p>{{ message }}</p>
      {% endfor %}
    </p>

    <p>
      Ici nous pourrons lire l'article ayant comme id : {{ id }}<br />
      Mais pour l'instant, nous ne savons pas encore le faire, cela viendra !
    </p>
  </body>
</html>

Essayez d'aller sur http://localhost/Symfony/web/app_dev.php/blog/ajouter, vous allez être redirigés et voir le message flash. Faites F5, et hop ! il a disparu.

Sachez également que le service « session » est aussi accessible depuis le service « request ». Ainsi, depuis un contrôleur vous pouvez faire :

<?php
$session = $this->getRequest()->getSession();

Les autres… et les nôtres !

Il existe évidemment bien d'autres services : nous les rencontrerons au fur et à mesure dans ce cours.

Mais il existera surtout nos propres services ! En effet, la plupart des outils que nous allons créer (un formulaire, un gestionnaire d'utilisateurs personnalisé, etc.) devront être utilisés plusieurs fois. Quoi de mieux, dans ce cas, que de les définir en tant que services ? Nous verrons cela dans la partie 4, mais sachez qu'après une petite étape de mise en place (configuration, quelques conventions), les services sont vraiment très pratiques !

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Manipuler l'objet Response Application : le contrôleur de notre blog

Application : le contrôleur de notre blog

Les différents services Le moteur de templates Twig

Construction du contrôleur

Notre blog est un bundle plutôt simple. On va mettre toutes nos actions dans un seul contrôleur « Blog ». Plus tard, nous pourrons éventuellement créer un contrôleur « Tag » pour manipuler les tags.

Malheureusement, on ne connaît pas encore tous les services indispensables. À ce point du cours, on ne sait pas réaliser de formulaire, manipuler les articles dans la base de données, ni même créer de vrais templates.

Pour l'heure, notre contrôleur sera donc très simple. On va créer la base de toutes les actions que l'on a mises dans nos routes. Je vous remets sous les yeux nos routes, et on enchaîne sur le contrôleur :

# src/Sdz/BlogBundle/Resources/config/routing.yml

sdzblog_accueil:
    path:      /{page}
    defaults:  { _controller: SdzBlogBundle:Blog:index, page: 1 }
    requirements:
        page:  \d*

sdzblog_voir:
    path:      /article/{id}
    defaults:  { _controller: SdzBlogBundle:Blog:voir }
    requirements:
        id:  \d+

sdzblog_ajouter:
    path:      /ajouter
    defaults:  { _controller: SdzBlogBundle:Blog:ajouter }

sdzblog_modifier:
    path:      /modifier/{id}
    defaults:  { _controller: SdzBlogBundle:Blog:modifier }
    requirements:
        id:  \d+

sdzblog_supprimer:
    path:      /supprimer/{id}
    defaults:  { _controller: SdzBlogBundle:Blog:supprimer }
    requirements:
        id:  \d+

Et le contrôleur « Blog » :

<?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($page)
  {
    // On ne sait pas combien de pages il y a
    // Mais on sait qu'une page doit être supérieure ou égale à 1
    if( $page < 1 )
    {
      // On déclenche une exception NotFoundHttpException
      // Cela va afficher la page d'erreur 404 (on pourra personnaliser cette page plus tard d'ailleurs)
      throw $this->createNotFoundException('Page inexistante (page = '.$page.')');
    }

    // Ici, on récupérera la liste des articles, puis on la passera au template

    // Mais pour l'instant, on ne fait qu'appeler le template
    return $this->render('SdzBlogBundle:Blog:index.html.twig');
  }
  
  
  public function voirAction($id)
  {
    // Ici, on récupérera l'article correspondant à l'id $id
    
    return $this->render('SdzBlogBundle:Blog:voir.html.twig', array(
      'id' => $id
    ));
  }
  
  public function ajouterAction()
  {
    // La gestion d'un formulaire est particulière, mais l'idée est la suivante :
    
    if( $this->get('request')->getMethod() == 'POST' )
    {
      // Ici, on s'occupera de la création et de la gestion du formulaire
      
      $this->get('session')->getFlashBag()->add('notice', 'Article bien enregistré');
    
      // Puis on redirige vers la page de visualisation de cet article
      return $this->redirect( $this->generateUrl('sdzblog_voir', array('id' => 5)) );
    }

    // Si on n'est pas en POST, alors on affiche le formulaire
    return $this->render('SdzBlogBundle:Blog:ajouter.html.twig');
  }
  
  public function modifierAction($id)
  {
    // Ici, on récupérera l'article correspondant à $id

    // Ici, on s'occupera de la création et de la gestion du formulaire

    return $this->render('SdzBlogBundle:Blog:modifier.html.twig');
  }

  public function supprimerAction($id)
  {
    // Ici, on récupérera l'article correspondant à $id

    // Ici, on gérera la suppression de l'article en question

    return $this->render('SdzBlogBundle:Blog:supprimer.html.twig');
  }
}

À retenir

L'erreur 404

Je vous ai donné un exemple qui vous montre comment déclencher une erreur 404. C'est quelque chose que l'on fera souvent, par exemple dès qu'un article n'existera pas, qu'un argument ne sera pas bon (page = 0), etc. Lorsque l'on déclenche cette exception, le noyau l'attrape et génère une belle page d'erreur 404. Vous pouvez aller voir l'annexe « Comment personnaliser ses pages d'erreur ».

La définition des méthodes

Nos méthodes vont être appelées par le noyau : elles doivent donc respecter le nom et les arguments que nous avons définis dans nos routes et se trouver dans le scope « public ». Vous pouvez bien entendu rajouter d'autres méthodes, par exemple pour exécuter une fonction que vous réutiliserez dans deux actions différentes. Dans ce cas, vous ne devez pas les suffixer de « Action » (afin de ne pas confondre).

Testons-le

Naturellement, seules les actions index et voir vont fonctionner, car nous n'avons pas créé les templates associés (ce sera fait dans le prochain chapitre). Cependant, nous pouvons voir le type d'erreur que Symfony2 nous génère. Allez sur la page de suppression d'un article, à l'adresse http://localhost/Symfony/web/app_dev.php/blog/supprimer/5. Vous pouvez voir que l'erreur est très explicite et nous permet de voir directement ce qui ne va pas. On a même les logs en dessous de l'erreur : on peut voir tout ce qui a fonctionné avant que l'erreur ne se déclenche. Notez par exemple le log n°4 :

Matched route "sdzblog_supprimer" (parameters: "_controller": "Sdz\BlogBundle\Controller\BlogController::supprimerAction", "id": "5", "_route": "sdzblog_supprimer")

On voit que c'est bien la bonne route qui est utilisée, super ! On voit aussi que le paramètre id est bien défini à 5 : re-super !

On peut également tester notre erreur 404 générée manuellement lorsque ce paramètre page est à 0. Allez sur http://localhost/Symfony/web/app_dev.php/blog/0, et admirez notre erreur. Regardez entre autres la toolbar (voir figure suivante).

La page n'existe pas, une erreur 404 est renvoyée
La page n'existe pas, une erreur 404 est renvoyée

Très pratique pour vérifier que tout est comme on l'attend !

Pour conclure

Créer un contrôleur à ce stade du cours n'est pas évident, car vous ne connaissez et ne maîtrisez pas encore tous les services nécessaires. Seulement, vous avez pu comprendre son rôle et voir un exemple concret.

Rassurez-vous, dans la partie 4 du tutoriel, on apprendra tout le nécessaire pour construire l'intérieur de nos contrôleurs. ;)
En attendant, rendez-vous au prochain chapitre pour en apprendre plus sur les templates.

Pour plus d'informations concernant les contrôleurs, n'hésitez pas à lire la documentation officielle.

En résumé

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Les différents services Le moteur de templates Twig

Le moteur de templates Twig

Application : le contrôleur de notre blog Les templates Twig

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

Le moteur de templates Twig Afficher des variables

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 tous les articles d'un blog, 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 :

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 template liste_news.html.twig, alors on saura afficher un fichier RSS en gardant le même contrôleur, mais en utilisant le template liste_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('SdzBlogBundle:Blog: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('SdzBlogBundle:Blog:email.txt.twig', array(
  'var1' => $var1,
  'var2' => $var2
));

// Puis on envoie l'e-mail, par exemple :
mail('[email protected]', 'Inscription OK', $contenu);

Et le template SdzBlogBundle:Blog:email.txt.twig contiendrait par exemple :

{# src/Sdz/BlogBundle/Resources/views/Blog/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 :

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

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Le moteur de templates Twig Afficher des variables

Afficher des variables

Les templates Twig Structures de contrôle et expressions

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, ici striptags est appliqué, puis title.

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

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

Utiliser un filtre avec des arguments.
Attention, il faut que date soit un objet de type Datetime ici.

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 seulement objet->getAttribut. En réalité, voici ce qu'elle fait exactement :

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 de Datetime.

{{ 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 filtre raw comme suit : {{ 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 quelques variables globales dans Twig pour nous faciliter la vie. Voici la liste des variables globales disponibles dans tous vos templates :

Variable

Description

{{ app.request }}

Le service « 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 }}

True si le mode debug est activé, False sinon.

{{ app.security }}

Le service « security », que nous verrons plus loin dans ce cours.

{{ 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. 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 configuration config.yml, mais dans le fichier des paramètres, à savoir parameters.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.

Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Les templates Twig Structures de contrôle et expressions

Structures de contrôle et expressions

Afficher des variables Hériter et inclure des templates

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 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 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 }}

true si c'est la première itération, false sinon.

{{ loop.last }}

true si c'est la dernière itération, false sinon.

{{ 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++;
}
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Afficher des variables Hériter et inclure des templates

Hériter et inclure des templates

Structures de contrôle et expressions Application : les templates de notre blog

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 les include(), 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 des include(), 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 dans src/Sdz/BlogBundle/Resources/views/layout.html.twig :

{# src/Sdz/BlogBundle/Resources/views/layout.html.twig #}

<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>{% block title %}SdzBlog{% endblock %}</title>
  </head>
  <body>

    {% block body %}
    {% endblock %}

  </body>
</html>

Voici un de nos templates fils. Mettons-le dans src/Sdz/BlogBundle/Resources/views/Blog/index.html.twig :

{# src/Sdz/BlogBundle/Resources/views/Blog/index.html.twig #}

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

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

{% block body %}
    OK, même s'il est pour l'instant un peu vide, mon blog sera trop bien !
{% 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 dans views/layout.html.twig et non dans views/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 plus SdzBlogBundle:MonController:layout.html.twig, mais SdzBlogBundle::layout.html.twig. C'est assez intuitif, en fait : on enlève juste la partie qui correspond au répertoire MonController. 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 :

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 :

Nous verrons un exemple de ce triple héritage juste après dans l'exemple du blog.

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 être app/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, cela n'est pas mal non plus. Prenons un exemple pour bien faire la différence.

Le formulaire pour ajouter un article est le même que celui pour… modifier un article. 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 templates SdzBlogBundle:Blog:ajouter.html.twig et SdzBlogBundle:Blog:modifier.html.twig qui héritent chacun de SdzBlogBundle::layout.html.twig. L'affichage exact de ces deux templates diffère un peu, mais chacun d'eux inclut SdzBlogBundle:Blog:formulaire.html.twig à l'endroit exact pour afficher le formulaire.

On voit bien qu'on ne peut pas faire d'héritage sur le template formulaire.html.twig, car il faudrait le faire hériter une fois de ajouter.html.twig, une fois de modifier.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 balise {% include %}, comme ceci :

{% include "SdzBlogBundle:Blog:formulaire.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 vue ajouter.html.twig :

{# src/Sdz/BlogBundle/Resources/views/Blog/ajouter.html.twig #}

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

{% block body %}

  <h2>Ajouter un article</h2>

  {% include "SdzBlogBundle:Blog:formulaire.html.twig" %}

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

{% endblock %}

Et voici le code du template inclus :

{# src/Sdz/BlogBundle/Resources/views/Blog/formulaire.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'article</h3>

{# Ici 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 blog, 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 du blog, on veut afficher les 3 derniers articles du blog.

C'est donc depuis le layout général qu'on va inclure non pas un template du bundle Blog — nous n'aurions pas les variables à lui donner —, mais un contrôleur du bundle Blog. 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 balise {% include %}, il faut utiliser la fonction {{ render() }}, comme ceci :

{{ render(controller("SdzBlogBundle:Blog:menu")) }}

Ici, SdzBlogBundle:Blog:menu n'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 :

{# app/Resources/views/layout.html.twig #}
<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>{% block title %}SdzBlog{% endblock %}</title>
  </head>
  <body>
    
    <div id="menu">
      {{ render(controller("SdzBlogBundle:Blog:menu")) }}
    </div>

    {% block body %}
    {% endblock %}

    </body>
</html>

Et du côté du contrôleur, c'est une méthode très classique (regardez la ligne 16) :

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

// …

  public function menuAction()
  {
    // On fixe en dur une liste ici, bien entendu par la suite on la récupérera depuis la BDD !
    $liste = array(
      array('id' => 2, 'titre' => 'Mon dernier weekend !'),
      array('id' => 5, 'titre' => 'Sortie de Symfony2.1'),
      array('id' => 9, 'titre' => 'Petit test')
    );
        
    return $this->render('SdzBlogBundle:Blog:menu.html.twig', array(
      'liste_articles' => $liste // C'est ici tout l'intérêt : le contrôleur passe les variables nécessaires au template !
    ));
  }

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

{# src/Sdz/BlogBundle/Resources/views/Blog/menu.html.twig #}

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

<ul>
<ul class="nav nav-pills nav-stacked">
  {% for article in liste_articles %}
    <li><a href="{{ path('sdzblog_voir', {'id': article.id}) }}">{{ article.titre }}</a></li>
  {% endfor %}
</ul>
</ul>
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.

Structures de contrôle et expressions Application : les templates de notre blog

Application : les templates de notre blog

Hériter et inclure des templates Installer un bundle grâce à Composer

Revenons à notre blog. 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 leur squelette. Étant donné que l'on n'a pas encore la vraie liste des articles, 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 all