Pour la première partie de ce tutoriel, c'est mon expérience de la mise en œuvre du guide d'installation de la page Symfony du présent site Web et de quelques autres trouvailles. Pour la seconde partie, c'est mon expérience sous Kubuntu 9.04 de la mise en œuvre du tutoriel de Symfony "Jobeet" que l'on retrouve à l'adresse Web : Practical symfony Jour 1: Démarrage du projet, de Fabien Potencier.
J'aurais bien aimé trouver l’équivalent du présent tutoriel (en français pour l'installation et la production d'un premier site Web avec Symfony) lors de mes premières recherches de "framework" il y à quatre mois, ou à mes débuts avec Symfony, il y a une semaine ! Je suis nouveau en ce domaine, vous le comprendrez ! Avec Apache, MySQL, PHP, framework MVC et compagnie, ça fait de gros morceaux à avaler pour moi, mais pas pour ce qui touche à la programmation-objet et en html !
Comme je souhaitais produire un modèle générique de base pour mes propres besoins éventuels, j'ai adapté les exemples du tutoriel pour un projet générique que je nomme Projet01. Cela m'a poussé à toujours bien comprendre les étapes, procédures et commandes, pour que le tout soit cohérent et bien sûr fonctionnel pour mes futurs projets. Je ne suis pas certain d'avoir tout bien réussi, mais pour moi cela est suffisamment potable pour être mise en ligne. Cette expérience sera probablement utile à d'autres. C'est ce que je souhaite !
Bon concert !
Dans ce document, on retrouve toutes les étapes à suivre pour amorcer un nouveau projet Symfony 1.2.8 sous Linux Kubuntu 9.04, incluant une première installation. Cela implique l'installation d'un serveur Apache, d'un système de gestion de base de données MySQL, du langage de programmation PHP, de l'administration avec PhpMyAdmin et l'outil de développement "framework" Symfony.
Si vous êtes sous Gnome ou Xfce, au lieu de KDE, la principale et peut-être la seule différence pour vous se trouve au niveau de l'éditeur de texte brut Kate que j'utilise dans les commandes d'édition à partir du terminal (console).
Si Apache, MySQL, PHP, PhpMyAdmin et Symfony sont déjà installés et configurés, sauter la section "Première installation" et passer à la section "Installer un nouveau projet".
Installez Apache et sa documentation.
Vérification du fonctionnement d'Apache en ouvrant votre navigateur internet à l'adresse Web suivante :
http://localhost/
Si tout s'est bien passé, vous devriez voir une page Web dans laquelle s'affiche le texte :
it works!
Je vous suggère d'ajouter cette adresse à vos "Marque-pages" (Signets, favoris), vous en aurez besoin plus tard.
Pour activer le mode en lecture et écriture "mod_rewrite" d'Apache :
sudo a2enmod rewrite
Installez le serveur de base de données MySQL.
À la fin de l'installation, une fenêtre vous demande de choisir le mot de passe pour l'utilisateur root de MySQL.
Entrer ce nouveau mot de passe pour ce nouveau compte "MySQL Root" et valider par la touche "Entrer".
Enregistrer ce nouveau mot de passe pour ce nouveau compte "MySQL Root" pour le retrouver au besoin.
Si cela n'est pas déjà dans votre habitude, je vous recommande d'enregistrer tous vos mots de passe dans un fichier Writer d'OpenOffice et d'enregistrer ce fichier avec un mot de passe. Pour se faire, dans OpenOffice, menu Fichier → Enregistrer sous…, cocher la boîte "Enregistrer avec mot de passe".
Par exemple, pour le mot de passe de "MySQL Root", j'ai les lignes suivantes dans mon fichier
MySQL : Nom d'utilisateur : Root Mot de passe : 12345678
Installez divers paquets pour la programmation en PHP5.
Éditer le fichier apache2.conf pour y ajouter le nom (adresse) du serveur :
sudo kate /etc/apache2/apache2.conf
Ajouter à la fin du fichier, la ligne suivante :
ServerName 127.0.0.1
Enregistrer le fichier et fermer le fichier.
Pour que ce changement soit pris en compte, il faut recharger le serveur apache2 avec la commande suivante dans un terminal :
sudo /etc/init.d/apache2 reload
Nous allons créer un programme PHP, le fichier "phpinfo.php" nous sera très utile pour nous renseigner sur l'installation et la configuration de PHP. Il est très simple, mais permettra de savoir du coût si PHP est correctement installé et fonctionnel.
Créez le fichier /var/www/phpinfo.php avec la commande suivante dans un terminal :
sudo kate /var/www/phpinfo.php
Copiez-y le code suivant :
<?php phpinfo(); ?>
Rééditer le fichier apache2.conf :
sudo kate /etc/apache2/apache2.conf
Copiez à la fin du fichier les lignes suivantes :
AddType application/x-httpd-php .php\\ AddType application/x-httpd-php-source .phps
Recharger le serveur apache2 :
sudo /etc/init.d/apache2 reload
Une fois le fichier "phpinfo.php" créé et enregistré, il apparaitre sur la page d'index de votre serveur. Entrez son adresse Web suivante dans votre navigateur préféré :
http://localhost/phpinfo.php
Je vous suggère d'ajouter cette adresse à vos "Marque-pages" (Signets, favoris), vous en aurez besoin plus tard !
Pour l'instant, on peut accéder aux bases de données MySQL avec le compte "root" et sans mot de passe, ce qui n'est pas très prudent et sécuritaire. Nous allons donc sécuriser un peu le serveur.
Si vous avez une erreur comme celle-là :
ERROR 2002 (HY000): Can't connect to local MySQL server through socket /var/run/mysqld/mysqld.sock' (2)
lancer MySQL avant de faire ces manipulations avec la commande suivante dans un terminal :
sudo /etc/init.d/mysql start
Exécutons le scripte de sécurisation avec la commande suivante dans un terminal :
sudo mysql_secure_installation
Des questions simples sont posées et il suffit d'y répondre puis de passer à la suivante.
À la question :
Enter current password for root (enter for none):
Il faut un mot de passe pour bien exécuter le script. Le mot de passe demandé précédemment en fin d'installation de MySQL est ce mot de passe root, il faut donc entrer celui-ci.
Validez votre entrée avec la touche "Entrée".
À la question :
Setting the root password ensures that nobody can log into the MySQL root user without the proper authorisation. Set root password? [Y/n]
Appuyez sur la touche "Entrée" pour entrer un mot de passe.
New password:
Entrez le mot de passe de votre choix et validez par la touche "Entrée".
À la question :
By default, a MySQL installation has an anonymous user, allowing anyone to log into MySQL without having to have a user account created for them. This is intended only for testing, and to make the installation go a bit smoother. You should remove them before moving into a production environment. Remove anonymous users? [Y/n]
Appuyez sur la touche « Entrée » pour supprimer l'utilisateur anonyme créé lors de l'installation.
À la question :
Normally, root should only be allowed to connect from 'localhost'. This ensures that someone cannot guess at the root password from the network. Disallow root login remotely? [Y/n]
Vous n'avez pas besoin d'administrer votre base de données à distance pour le moment, ce qui devrait être le cas de la plupart des utilisateurs. Validez par la touche "Entrée". Ainsi, l'utilisateur "root" ne pourra se connecter qu'en local. Sinon tapez la touche "n".
À la question :
Remove test database and access to it? [Y/n]
Validez par la touche "Entrée" pour supprimer la base de données de test créée lors de l'installation.
À la question :
Reloading the privilege tables will ensure that all changes made so far will take effect immediately. Reload privilege tables now? [Y/n]
Validez par la touche "Entrée" pour prendre en compte les changements que vous venez de faire et les appliquer immédiatement.
ATTENTION : Pendant l'installation de PhpMyAdmin, il vous sera demandé de choisir le serveur Web à configurer. Choisissez avec les touches Flèche vers le haut et Flèche vers le bas la ligne "apache2" et appuyant sur la barre d'espacement la sélectionné cette entrée.
Pour vérifier le bon fonctionnement de PhpMyAdmin, rendez-vous à l'URL :
http://localhost/phpmyadmin/
si vous tombez sur une page (not found), Tapez la command
sudo kate /etc/apache2/apache2.conf
et ajoutez à la fin un nouveau ligne
Include /etc/phpmyadmin/apache.conf
Vous arriverez alors sur la page d'authentification de PhpMyAdmin. Entrez alors votre non de compte qui est
root
et le mot de passe choisi lors de la sécurisation de MySQL
Remarque : si votre compte est dupont ne pas mettre dupont comme utilisateur, mais "root" puis le mot de passe sinon vous risquez d'avoir l'erreur #1045 - Access denied for user 'root'@'localhost' en tentant d'accéder à vos bases de données.
- Si tout s'est bien passé, vous devriez être connecté à PhpMyAdmin et pouvoir créer et gérer vos bases de données. - Si vous arrivez pas à vous connecter à PhpMyAdmin il faut créer un lien symbolique vers MyPhpAdmin, il faut juste faire la commande suivante: " sudo ln -s /usr/share/phpmyadmin /var/www/phpmyadmin " Il existe déjà deux bases de données, n'y touchez pas. Elles servent au bon fonctionnement de MySQL.
Je vous suggère d'ajouter cette adresse à vos "Marque-pages" (Signets, favoris), vous en aurez besoin plus tard !
Cette section est utile seulement si vous mettez en ligne votre projet Web et que votre ordinateur devient un serveur en ligne sur Internet, ce qui n'est pas expliqué dans le présent tutoriel.
Le répertoire contenant les sites lus par Apache est par défaut le suivant :
/var/www/
Ses droits par défaut sont :
propriétaire=root group=root droits rwXr-Xr-X
Les X signifie droit x pour les répertoires, mais pas pour les fichiers.
Le groupe utilisateur pour Apache est "www-data".
Pour accéder aux fichiers qu'il doit lire, Apache utilise donc en standard le droit "r", mais seul root peut modifier ces fichiers, ce qui n'est pas très pratique. Un ajustement de la politique des droits permet de faciliter l'utilisation de ce répertoire.
Il faut commencer par s'ajouter au groupe d'Apache, le groupe "www-data", alors dans un terminal, on exécute la commande suivante :
sudo addgroup $USER www-data
Par la suite, on modifie les droits du dossier et des fichiers avec les commandes suivantes :
sudo chown -R www-data:www-data /var/www sudo chmod -R 770 /var/www
Il faut actualiser fermer et rouvrir votre gestionnaire de fichiers (Dolpin ou Konqueror) pour que cela soit pris en compte.
Vous avez maintenant un serveur Apache gérant le PHP avec des bases de données MySQL.
Installez des paquets et dépendances pour le cadre de travail "framework" Symfony.
Pour configurer Symfony avec PEAR, entrer les commandes :
sudo pear channel-discover pear.symfony-project.com
sudo pear install symfony/symfony
Si tout s'est bien passé, vous aurez le message suivant :
install ok: channel://pear.symfony-project.com/symfony-1.x.x
Tapez cette comand dans le terminal :
sudo pear install --alldeps http://phing.info/pear/phing-current.tgz
Nous avons un premier niveau d'aide pour Symfony avec la commande suivante :
symfony
La commande suivante nous donne la version installée de Symfony :
symfony -V
C'est tout ce qu'il faut pour Symfony !
Pour notre exemple, nous créons le projet "projet01", dans le dossier "projet01" de votre dossier utilisateur principal (/home/votre_nom_d'utilisateur/projet01).
sudo mkdir /home/votre_nom_d'utilisateur/projet01
Chez moi, cela pourrait donner comme chemin de dossier ce qui suit :
/home/rene/projet01
Si vous procédez différemment, vous prendrez soin d'adapter tout ce qui suit avec votre propre chemin de dossier.
Pour nous donner le plus de latitude possible et pour pouvoir travailler sur plusieurs projets à la fois, nous créons un domaine local virtuel pour chaque projet. Vous pourrez procéder de la même manière pour chacun de vos projets de site Web.
Ouvrer le fichier /etc/hosts avec la commande suivante dans un terminal :
sudo kate /etc/hosts
Puis ajoutez à la fin de la ligne qui commence par "127.0.0.1", le nom de domaine de notre projet01 :
dev.projet01.com
Créez un nouveau fichier "projet01" pour le serveur virtuel Apache avec la commande suivante dans un terminal :
sudo kate /etc/apache2/sites-available/projet01
Copiez-y les lignes suivantes en remplacent le mot "rene" par votre_nom_utilisateur et projet01 par votre_dossier_projet :
<VirtualHost dev.projet01.com:80> ServerName dev.projet01.com DocumentRoot "/home/rene/projet01/web/" DirectoryIndex index.php <Directory "/home/rene/projet01/web/"> AllowOverride All Allow from All </Directory> Alias /sf /usr/share/php/data/symfony/web/sf/ <Directory "/usr/share/php/data/symfony/web/sf/"> AllowOverride All Allow from All </Directory> </VirtualHost>
Activons ce nouveau domaine avec la commande suivante dans un terminal :
sudo a2ensite projet01
Pour que ces changements soient pris en compte, il faut relancer le serveur Apache avec la commande suivante :
sudo /etc/init.d/apache2 restart
Entrez l'adresse Web du nouveau projet dans votre navigateur préféré :
dev.projet01.com
Je vous suggère d'ajouter cette adresse à vos "Marque-pages" (Signets, favoris), vous en aurez besoin plus tard !
Assurez-vous d'être à la racine du dossier "projet01" !
Créons maintenant le canevas de base avec Symfony avec la commande suivante dans le terminal :
sudo symfony generate:project projet01 --orm=Propel
Si vous avez un message d'avertissement pensez à avoir les droits (sudo) car symfony ne dit toujours qu'il faut avoir les permissions
Cette commande génère la structure par défaut des répertoires et crée les fichiers nécessaires d'un projet symfony.
Dossier | Description |
---|---|
apps | Contient les applications du projet |
cache | Les fichiers en cache |
config | Les fichiers de configuration du projet |
lib | Les librairies et classes du projet |
log | Les fichiers de logs du framework |
plugins | Les plugins installés |
test | Les tests unitaires et fonctionnels |
web | Le répertoire racine web (voir dessous) |
Pourquoi autant de fichiers ? Un des bénéfices est de standardiser les développements. Grâce à cette structure par défaut des fichiers et répertoires de symfony, n'importe quel développeur connaissant symfony peut reprendre n'importe quel projet. Il pourra naviguer dans le code, de fixer les bogues, et d'ajouter de nouvelles fonctionnalités, etc.
À la racine du projet, un raccourci la commande symfony y est pour faciliter l'écriture des commandes lorsque vous exécutez une tâche dans le terminal. Toutes les commandes symfony doivent maintenant être exécutées à partir du répertoire racine du projet, sauf indication contraire.
D’abord assurez vous que vous situés sous le répertoire projet symfone, dans notre cas projet01 :
cd /home/nomVotrePC/projet01
Créons l'application frontend en exécutant la commande suivante :
sudo symfony generate:app --escaping-strategy=on --csrf-secret=Unique$ecret frontend
Cette commande crée la structure par défaut des répertoires de l'application dans le dossier apps/frontend.
Dossier | Description |
---|---|
config | Les fichiers de configuration de l'application |
lib | Les librairies et classes de l'application |
modules | Le code de l'application ( MVC ) |
templates | Les gabarits principaux |
Avec cette commande nous avons passé deux paramètres en options pour la sécurité, "–escaping-strategy" pour activer les échappements pour prévenir des attaques XSS et "–csrf-secret" pour activer les jetons de sessions des formulaires pour prévenir des attaques CSRF.
En passant ces deux options, nous avons sécurisé le développement des deux plus courantes vulnérabilités trouvées sur le web. Symfony va automatiquement prendre les mesures de sécurité pour nous.
Lors de la portabilité du projet sur hébergement sur le Web, nous changerons le chemin absolu de l'installation de symfony par un chemin relatif.
Pour ce faire, vous ouvrirez le fichier requis avec la commande :
kate config/ProjectConfiguration.class.php
Remplacerez la ligne :
require_once '/usr/share/php/symfony/autoload/sfCoreAutoload.class.php';
Par la ligne suivante qui est un chemin relatif si vous y copier le dossier de l'archive de Symfony dans le dossier /home/rene/projet01/lib/vendor :
require_once dirname(__FILE__).'/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
De cette façon, nous pourrons déplacer le projet, cela fonctionnera toujours.
Si vous jetez un oeil au répertoire web/, vous trouvez deux fichiers PHP, soit index.php et frontend_dev.php. Ces fichiers sont appelés front controllers. Toutes les requêtes vers l'application sont faites en passant par eux.
Mais pourquoi avoir deux front controllers alors que nous n'avons qu'une seule application ?
Les deux fichiers pointent sur la même application, mais en utilisant deux environnements différents. Lorsque vous développez une application, sauf si vous développez directement sur le serveur de production, vous avez besoin de plusieurs environnements.
- L'environnement de développement. C'est l'environnement utilisé par le développeur pour ajouter de nouvelles fonctionnalités, fixer les bogues, etc.
- L'environnement de tests. C'est l'environnement pour tester automatiquement l'application.
- L'environnement de recette. C'est l'environnement utilisé par le client pour tester l'application et faire un retour sur les bogues.
- L'environnement de production. C'est l'environnement utilisé par l'utilisateur final.
Dans l'environnement de développement, l'application doit centraliser tous les détails d'une requête pour faciliter le débogage, elle doit afficher les exceptions dans le navigateur, mais le système de cache doit être désactivé, car les changements dans le code doivent être pris en compte tout de suite. Donc l'environnement de développement doit être optimisé pour le développeur.
Dans l'environnement de production, l'application doit afficher des messages d'erreur adaptés à la place d'exceptions PHP, et bien sûr, le cache doit être activé. Donc l'environnement de production doit être optimisé pour la performance et l'expérience utilisateur.
Dans Symfony un environnement est un jeu unique de paramètres de configuration et Symfony est installé avec trois d'entre eux : dev, test, et prod.
Ouvrer fichier du schéma de la base de données avec la commande :
kate config/schema.yml
Copiez-y le texte suivant :
propel: projet01_category: id: ~ name: { type: varchar(255), required: true } projet01_job: id: ~ category_id: { type: integer, foreignTable: projet01_category, foreignReference: id, required: true } type: { type: varchar(255) } company: { type: varchar(255), required: true } logo: { type: varchar(255) } url: { type: varchar(255) } position: { type: varchar(255), required: true } location: { type: varchar(255), required: true } description: { type: longvarchar, required: true } how_to_apply: { type: longvarchar, required: true } token: { type: varchar(255), required: true, index: unique } is_public: { type: boolean, required: true, default: 1 } is_activated: { type: boolean, required: true, default: 0 } email: { type: varchar(255), required: true } expires_at: { type: timestamp, required: true } created_at: ~ updated_at: ~ projet01_affiliate: id: ~ url: { type: varchar(255), required: true } email: { type: varchar(255), required: true, index: unique } token: { type: varchar(255), required: true } is_active: { type: boolean, required: true, default: 0 } created_at: ~ projet01_category_affiliate: category_id: { type: integer, foreignTable: projet01_category, foreignReference: id, required: true, primaryKey: true, onDelete: cascade } affiliate_id: { type: integer, foreignTable: projet01_affiliate, foreignReference: id, required: true, primaryKey: true, onDelete: cascade }
Avec mysqladmin, nous créons la base de données avec la commande suivante <B>en prenant soin de modifier la commande. Si votre nom d'utilisateur MySQLAdmin est "1111" et que votre mot de passe MySQLAdmin est "22222222"</B> le commande sera :
sudo mysqladmin -u1111 -p2222222 create projet01
Pour indiquer à Symfony la base de données que nous utilisons pour le projet, entrons la commande suivante <B>en prenant soin de modifier la commande. Si votre nom d'utilisateur MySQLAdmin est "1111" et que votre mot de passe MySQLAdmin est "22222222"</B> la commande sera :
sudo symfony configure:database "mysql:host=localhost;dbname=projet01" 1111 22222222
Avec la description de la base de données dans le fichier schema.yml, nous pouvons utiliser les tâches intégrées de l'ORM pour générer les déclarations SQL nécessaires pour créer des tables :
symfony propel:build-sql
Cette commande génère le déclarations SQL dans le répertoire data/sql, optimisées pour le moteur de base de données que nous avons configuré.
Pour créer les tables dans la base de données, entrer la commande suivante dans le terminal :
symfony propel:insert-sql --no-confirmation
L'ORM génère également les classes PHP qui font la correspondance entre les enregistrements des tables et les objets
Générer les fichiers PHP qui seront utilisés pour interagir avec la base de données dans le répertoire lib/model avec la commande suivante :
symfony propel:build-model
En navigant parmi les fichiers générés, vous avez probablement remarqué que Propel génère quatre classes par table.
Les valeurs des colonnes d'un enregistrement peuvent être manipulées par l'objet en utilisant un accesseur (accessors get*() méthode) ou un mutateur (mutators set*() méthode):
$job = new Projet01Job(); $job->setPosition('Web developer'); $job->save(); echo $job->getPosition(); $job->delete();
Vous pouvez également définir les clés étrangères en liant les objets entre eux :
$category = new Projet01Category(); $category->setName('Programming'); $job = new Projet01Job(); $job->setCategory($category);
La tâche propel:build-all est un raccourci pour exécuter les tâches que nous avons vues dans cette section et d'autres. Donc, exécutez cette tâche maintenant pour générer les formulaires et les validateurs du modèle des classes :
symfony propel:build-all
La tâche propel:build-all-load est un raccourci pour la tâche propel:build-all suivie de la tâche propel:data-load.
Comme nous le verrons un peu plus tard, Symfony charge automatiquement les classes PHP pour vous, ce qui signifie que vous n'avez jamais besoin d'utiliser require dans votre code. C'est une des nombreuses choses que Symfony automatise pour les développeurs, mais il y a une contre partie : Chaque fois que vous ajoutez une classe vous devez effacer le cache. Comme la tâche propel:build-model a créé de nouvelles classes, effaçons la cache :
symfony cache:clear
Une tâche Symfony est composée d'un espace de nom et d'un nom de tâche. Chaque tâche a un raccourci avec le moins d'ambiguïté avec les autres tâches. La commande suivante est équivalente à cache:clear:
symfony cc
Les tables ont été créées dans la base de données, mais elles sont vides. Pour chaque application web, il y a trois types de données :
- Les données initiales : Les données initiales sont nécessaires pour que l'application fonctionne. Par exemple, notre projet01 a besoin de catégories, sinon personne ne pourra soumettre un poste. Nous avons également besoin d'un administrateur capable de s'authentifier au backend.
- Les données de test : Les données de test sont nécessaires pour tester l'application. Comme développeur vous allez écrire des tests pour être certain que tout se comporte comme prévu. Le meilleur moyen est d'écrire des tests automatiques. Donc à chaque fois que vous faites des tests, vous avez besoin d'une base de données propre avec des données actualisées.
- Les données utilisateurs : Les données utilisateurs sont créées par les utilisateurs pendant la vie normale de l'application.
Chaque fois que Symfony crée des tables dans la base de données, les données sont perdues. Pour envoyer des données initiales à la base nous pouvons écrire un script PHP, ou exécuter du code SQL avec le programme MySQL.
Mais comme le besoin est commun, il y a une meilleure méthode avec Symfony. Créez un fichier YAML dans le répertoire data/fixtures/ et utilisez la tâche propel:data-load pour les envoyer dans la base de données.
Créer les deux fichiers suivants avec leur contenu respectif dans le dossier data/fixtures/ :
Fichier 1) 010_categories.yml
Projet01Category: design: { name: Design } programming: { name: Programming } manager: { name: Manager } administrator: { name: Administrator }
Fichier 2) 020_jobs.yml
Projet01Job: job_sensio_labs: category_id: programming type: full-time company: Sensio Labs logo: sensio_labs.png url: http://www.sensiolabs.com/ position: Web Developer location: Paris, France description: | You've already developed websites with symfony and you want to work with Open-Source technologies. You have a minimum of 3 years experience in web development with PHP or Java and you wish to participate to development of Web 2.0 sites using the best frameworks available. how_to_apply: | Send your resume to fabien.potencier [at] sensio.com is_public: true is_activated: true token: job_sensio_labs email: job@example.com expires_at: 2010-10-10 job_extreme_sensio: category_id: design type: part-time company: Extreme Sensio logo: extreme_sensio.png url: http://www.extreme-sensio.com/ position: Web Designer location: Paris, France description: | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in. Voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. how_to_apply: | Send your resume to fabien.potencier [at] sensio.com is_public: true is_activated: true token: job_extreme_sensio email: job@example.com expires_at: 2010-10-10
Un fichier de jeu de données tests est écrit en YAML. Il utilise le modèle des objets avec un nom unique pour chaque enregistrement. Ce nom est utile pour lier les objets relationnels entre eux sans à avoir à définir une clé primaire (qui sont souvent des champs auto-incrémentés et ne peuvent être définis). Par exemple, le poste job_sensio_labs utilise la catégorie programming, ce qui correspond à la catégorie 'Programming' .
Un fichier de jeu de données peut contenir des objets d'un ou plusieurs modèles.
Remarquez le nombre qui préfixe le nom du fichier. C'est un moyen simple de contrôler l'ordre de chargement des données. Plus tard si nous avons besoin d'insérer un nouveau jeu, ce sera plus facile, car nous avons des numéros libres entre ceux existants.
Dans un fichier de jeu de données, vous n'avez pas besoin de définir toutes les valeurs de colonnes. Dans ce cas Symfony, va utiliser la valeur par défaut indiquée dans le schéma de la base de données. Et comme Symfony utilise un Propel pour charger les données en base, tous les comportements à la création (comme pour created_at ou updated_at) ou les comportements que vous aurez ajoutés au modèle sont activés.
Charger les données initiales dans la base de données est aussi simple que d'exécuter la commande suivante :
symfony propel:data-load
Le voir en action dans le navigateur
Nous avons beaucoup utilisé l'interface de ligne de commande, mais ce n'est pas vraiment excitant, surtout pour un projet web. Nous avons maintenant tout ce qu'il nous faut pour créer des pages web qui vont interagir avec la base de données.
Voyons comment afficher la liste des postes, comment éditer un poste existant, et comment l'effacer. Comme nous l'avons expliqué, un projet Symfony est fait d'application. Chacune d'entre elles est faite de module. Un module contient un jeu de code PHP qui représente une fonctionnalité de l'application (le module de l'API par exemple) ou un jeu de manipulation sur le modèle objet que peut faire un utilisateur (le module des postes par exemple).
Symfony est capable de générer automatiquement pour un modèle un module qui fournit des fonctionnalités basiques. Entrer les commandes suivantent pour les frontend affiliate, category et job :
symfony propel:generate-module --with-show --non-verbose-templates frontend affiliate Projet01Affiliate
symfony propel:generate-module --with-show --non-verbose-templates frontend category Projet01Category
symfony propel:generate-module --with-show --non-verbose-templates frontend category_affiliate Projet01CategoryAffiliate
symfony propel:generate-module --with-show --non-verbose-templates frontend job Projet01Job
Cette commande génère un module job dans l'application frontend sur le modèle de Projet01Job. Comme pour la plupart des tâches Symfony, des répertoires et des fichiers ont été créés pour vous dans le répertoire apps/frontend/modules/job :
Directory | Description |
---|---|
actions | Les actions du module |
templates | Les gabarits du module |
Le fichier actions/actions.class.php définit toutes les actions disponibles pour le module job :
Action name | Description |
---|---|
index | Affiche les enregistrements de la table |
show | Affiche les champs d'un enregistrement donné |
new | Affiche un formulaire pour créer un nouvel enregistrement |
create | Créer un nouvel enregistrement |
edit | Affiche un formulaire pour éditer un enregistrement |
update | Mise à jour d'un enregistrement avec les données soumises |
delete | Efface un enregistrement donné |
Vous pouvez tester dès maintenant les modules dans votre navigateur :
http://dev.projet01.com/frontend_dev.php/affiliate
http://dev.projet01.com/frontend_dev.php/category
http://dev.projet01.com/frontend_dev.php/category_affiliate
http://dev.projet01.com/frontend_dev.php/job
Je vous suggère d'ajouter ces adresses à vos "Marque-pages" (Signets, favoris), vous en aurez besoin plus tard.
La mise en page par défaut d'une application est appelée layout.php et se trouve dans le dossier "apps/frontend/templates/". Ce dossier contient tous les templates globaux pour une application.
Avec la commande :
kate apps/frontend/templates/layout.php
Remplacez le contenu par défaut du fichier par le code suivant :
<!-- apps/frontend/templates/layout.php --> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>01</title> <link rel="shortcut icon" href="/favicon.ico" /> <?php include_javascripts() ?> <?php include_stylesheets() ?> </head> <body> <div id="container"> <div id="header"> <div class="content"> <h1><a href="<?php echo url_for('job/index') ?>"> <img src="/images/logo.jpg" alt="Projet01" /> </a></h1> <div id="sub_header"> <div class="post"> <h2>Ask for people</h2> <div> <a href="<?php echo url_for('job/index') ?>">Post a Job</a> </div> </div> <div class="search"> <h2>Ask for a job</h2> <form action="" method="get"> <input type="text" name="keywords" id="search_keywords" /> <input type="submit" value="search" /> <div class="help"> Enter some keywords (city, country, position, ...) </div> </form> </div> </div> </div> </div> <div id="content"> <?php if ($sf_user->hasFlash('notice')): ?> <div class="flash_notice"><?php echo $sf_user->getFlash('notice') ?></div> <?php endif; ?> <?php if ($sf_user->hasFlash('error')): ?> <div class="flash_error"><?php echo $sf_user->getFlash('error') ?></div> <?php endif; ?> <div class="content"> <?php echo $sf_content ?> </div> </div> <div id="footer"> <div class="content"> <span class="symfony"> <img src="/images/jobeet-mini.png" /> powered by <a href="http://www.symfony-project.org/"> <img src="/images/symfony.gif" alt="symfony framework" /></a> </span> <ul> <li><a href="">About de Projet01</a></li> <li class="feed"><a href="">Full feed</a></li> <li><a href="">Projet01 API</a></li> <li class="last"><a href="">Affiliates</a></li> </ul> </div> </div> </div> </body> </html>
Un template Symfony n'est ni plus ni moins qu'un fichier PHP. Dans le template layout, vous trouverez des appels à des fonctions PHP et des références à des variables PHP. sf_content est la variable la plus intéressante : elle est définie par le framework lui-même et contient le code HTML généré par l'action.
Si vous parcourez le module job avec votre navigateur :
http://dev.projet01.com/frontend_dev.php/job
Vous verrez que toutes les actions sont mises en page selon le modèle défini dans le layout.
Nous utiliserons les images et les fichiers CSS du tutoriel Jobeet, sans les modifier pour ne pas alourdir le présent texte. Vous de jouer avec ces fichiers si le coeur vous en dit.
Télécharger et décompresser les archives et l'image suivantes :
http://www.symfony-project.org/get/jobeet/images.zip
Copier le dossier "images" sur le dossier "web/images"
http://www.symfony-project.org/get/jobeet/css.zip
Copier le dossier "css" sur le dossier "web/css"
http://www.symfony-project.org/images/jobeet/favicon.ico
Copier l'image "favicon.ico" dans le dossier "web/images"
Vider la cache avec la commande :
symfony cc
Recharger les pages pour les voir enjolivée des nouveaux ajouts.
http://dev.projet01.com/frontend_dev.php/affiliate
http://dev.projet01.com/frontend_dev.php/category
http://dev.projet01.com/frontend_dev.php/category_affiliate
http://dev.projet01.com/frontend_dev.php/job
Par défaut, la tâche generate:project a créé trois dossier pour le projet courant : web/images/ pour les images, web/css/ pour les feuilles de style, et web/js/ pour les fichiers Javascript. Ceci fait partie des nombreuses conventions définies par Symfony, mais vous pouvez évidemment les placer dans un autre dossier sous le répertoire web/, mais cela n'est pas recommandé.
Bien que le fichier main.css n'est défini nul part dans le layout par défaut, il est nécessairement présent dans le code HTML généré. Mais pas les autres. Comment est-ce possible ?
La feuille de style a été inclue grâce à la fonction include_stylesheets() située entre les balises <head> du fichier layout. La fonction include_stylesheets() est appelée un helper. Un helper est une fonction, définie par symfony, pouvant prendre des paramètres et renvoyant du code HTML. La plupart du temps, les helpers permettent de gagner du temps, ils contiennent du code fréquemment utilisé dans les templates. Ici, le helper include_stylesheets() génère une balise <link> spécifique aux feuilles de style.
Mais comment le helper sait quelle feuille de style inclure ?
La couche vue peut être paramétrée en éditant le fichier de configuration view.yml de l'application. Voici le fichier par défaut généré lors de l'appel à la tâche generate:app
# apps/frontend/config/view.yml default: http_metas: content-type: text/html metas: #title: symfony project #description: symfony project #keywords: symfony, project #language: en #robots: index, follow stylesheets: [main.css] javascripts: [] has_layout: on layout: layout
Le fichier view.yml contient les paramètres par défaut (default) pour tous les templates de l'application. Par exemple, l'entrée `stylesheets' définit un tableau de fichiers de feuilles de style à inclure pour toutes les pages de l'application (ceci grâce au helper vu ci-dessus).
Dans le fichier de configuration par défaut view.yml, le fichier référence est main.css, et non pas css/main.css. En fait, les deux définitions sont équivalentes. Symfony préfixe les chemin relatifs avec /css/.
Si plusieurs fichiers sont définis, Symfony les inclura dans le même ordre que celui dans lequel ils ont été définis :
stylesheets: [main.css, jobs.css, job.css]
Vous pouvez également définir l'attribut media et omettre le suffixe .css :
stylesheets: [main.css, jobs.css, job.css, print: { media: print }]
Cette configuration génèrera le code suivant :
<link rel="stylesheet" type="text/css" media="screen" href="/css/main.css" /> <link rel="stylesheet" type="text/css" media="screen" href="/css/jobs.css" /> <link rel="stylesheet" type="text/css" media="screen" href="/css/job.css" /> <link rel="stylesheet" type="text/css" media="print" href="/css/print.css" />
Le fichier de configuration view.yml définit également le layout utilisé par défaut pour l'application. Par défaut, son nom est layout. Par conséquent, Symfony met en page chacune de vos pages à partir du fichier layout.php. Vous pouvez également désactiver cette mise en page en définissant l'entrée has_layout à false.
Tel quel, le site répond à nos attentes. Le fichier jobs.css est uniquement utile pour la page d'accueil, et le fichier job.css uniquement pour la page job. Il peut-être intéressant de faire en sorte que chaque fichier ne soit inclu que lorsqu'il est utile. Le fichier de configuration view.yml peut résoudre ce problème en le personnalisant par module.
Sous les sections indexSuccess et showSuccess (qui sont les noms des templates associés aux actions index et show, comme nous le verrons plus tard), vous pouvez personnaliser les entrées se trouvant sous la section default du fichier view.yml de l'application. Toutes les entrées spécifiques sont fusionnées avec celles de l'application. Vous pouvez également définir des paramètres pour toutes les actions d'un module avec la section spéciale all.
Pour beaucoup de fichiers de configuration de Symfony, un même paramètre peut être définit à différents niveaux :
- Au niveau du framework lui-même - Au niveau du projet (dans le répertoire config/) - A un niveau plus local, celui de l'application (dans le répertoire apps/APP/config/) - Au niveau restreint au module (dans le répertoire apps/APP/modules/MODULE/config/)
Lors de l'exécution, le système de configuration fusionne tous les paramètres depuis les différents fichiers si ils existent et les met en cache pour de meilleurs performances.
En règle générale, quand quelque chose est configurable via un fichier de configuration, la même chose peut être faite avec du code PHP. Au lieu de créer un fichier view.yml pour le module job par exemple, vous pouvez aussi utiliser le helper use_stylesheet() pour inclure une feuille de style depuis un template avec par exemple la ligne :
<?php use_stylesheet('main.css') ?>
Vous pouvez également utiliser ce helper dans le layout pour inclure une feuille de style globale à l'application.
Le choix entre une méthode ou l'autre est réellement une question de goût. Le fichier view.yml permet de définir quelque chose pour toutes les actions d'un module, ce qui n'est pas possible depuis un template. Cela dit, la configuration est plus statique. A l'inverse, le helper use_stylesheet() est plus flexible et plus encore, tout se trouve au même endroit : la définition des feuilles de style et le code HTML. Pour Projet01, nous allons utiliser le helper use_stylesheet(), et mettre à jour le template job avec les appels à use_stylesheet().
De la même manière, la configuration JavaScript est faite via l'entrée javascripts du fichier de configuration view.yml ou via le helper use_javascript() permettant d'inclure des fichiers JavaScript dans un template.
La page d'accueil Job
Comme déjà vu, la page d'accueil est générée par l'action index du module job. L'action index fait partie de la couche Contrôleur de la page et le template associé, indexSuccess.php, fait parti de la couche Vue :
apps/ frontend/ modules/ job/ actions/ actions.class.php templates/ indexSuccess.php
Chaque action est représentée par une méthode de classe. Pour la page d'accueil de job, la classe est jobActions (le nom du module avec le suffixe Actions) et la méthode est executeIndex() (le nom de l'action avec le préfixe execute). Dans notre cas, cela renvoie tous les jobs de la BDD :
// apps/frontend/modules/job/actions/actions.class.php class jobActions extends sfActions { public function executeIndex(sfWebRequest $request) { $this->projet01_job_list = Projet01JobPeer::doSelect(new Criteria()); } // ... }
Analysons de plus près le code : la méthode executeIndex() (couche Contrôleur) appelle la (couche) Modèle Projet01JobPeer pour renvoyer tous les jobs (new Criteria()). Le modèle renvoie un tableau d'objet de type Projet01Job que l'on affecte à la propriété projet01_job_list de l'objet courant.
Toutes les propriétés des objets sont automatiquement passées au template (la couche Vue). Pour transmettre des données du Contrôleur à la Vue, il vous suffit simplement de créer une nouvelle propriété :
public function executeFooBar(sfWebRequest $request) { $this->foo = 'bar'; $this->bar = array('bar', 'baz'); }
Cette méthode rendra les variables $foo et $bar accessibles depuis le template. Le Template
Par défaut, le nom du template associé à l'action est déduit par symfony : le nom de l'action avec le suffixe Success.
Le template indexSuccess.php génère une table HTML pour tous les jobs :
<!-- apps/frontend/modules/job/templates/indexSuccess.php --> <h1>Job List</h1> <table> <thead> <tr> <th>Id</th> <th>Category</th> <th>Type</th> <!-- more columns here --> <th>Created at</th> <th>Updated at</th> </tr> </thead> <tbody> <?php foreach ($projet01_job_list as $projet01_job): ?> <tr> <td> <a href="<?php echo url_for('job/show?id='.$projet01_job->getId()) ?>"> <?php echo $projet01_job->getId() ?> </a> </td> <td><?php echo $projet01_job->getCategoryId() ?></td> <td><?php echo $projet01_job->getType() ?></td> <!-- more columns here --> <td><?php echo $projet01_job->getCreatedAt() ?></td> <td><?php echo $projet01_job->getUpdatedAt() ?></td> </tr> <?php endforeach; ?> </tbody> </table> <a href="<?php echo url_for('job/new') ?>">New</a>
Dans ce code, la boucle foreach parcourt la liste d'objets job ($projet01_job_list) et, pour chaque job, chaque colonne est affichée.
Souvenez-vous, pour accéder à la valeur d'une colonne (propriété), il suffit simplement de faire appel à un accesseur. Comme d'habitude, le nom de ces accesseurs suit une convention établit par Symfony : chaque accesseur commence par le préfixe get suivit du nom de la colonne (propriété) en camelCased (par exemple, la méthode getCreatedAt() permet d'accéder à la valeur de la colonne created_at de l'objet).
Faisons un peu de tri dans tout ça afin de n'afficher qu'une partie des propriétés :
<!-- apps/frontend/modules/job/templates/indexSuccess.php --> <?php use_stylesheet('jobs.css') ?> <div id="jobs"> <table class="jobs"> <?php foreach ($projet01_job_list as $i => $job): ?> <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>"> <td class="location"><?php echo $job->getLocation() ?></td> <td class="position"> <a href="<?php echo url_for('job/show?id='.$job->getId()) ?>"> <?php echo $job->getPosition() ?> </a> </td> <td class="company"><?php echo $job->getCompany() ?></td> </tr> <?php endforeach; ?> </table> </div>
La fonction url_for() utilisée dans ce template est un helper Symfony.
Personnalisons maintenant le template de la page d'un job. Ouvrez le fichier showSuccess.php avec la commande
kate apps/frontend/modules/job/templates/showSuccess.php
Remplacez son contenu par le code suivant :
<?php use_stylesheet('job.css') ?> <?php use_helper('Text') ?> <div id="job"> <h1><?php echo $job->getCompany() ?></h1> <h2><?php echo $job->getLocation() ?></h2> <h3> <?php echo $job->getPosition() ?> <small> - <?php echo $job->getType() ?></small> </h3> <?php if ($job->getLogo()): ?> <div class="logo"> <a href="<?php echo $job->getUrl() ?>"> <img src="/uploads/jobs/<?php echo $job->getLogo() ?>" alt="<?php echo $job->getCompany() ?> logo" /> </a> </div> <?php endif; ?> <div class="description"> <?php echo simple_format_text($job->getDescription()) ?> </div> <h4>How to apply?</h4> <p class="how_to_apply"><?php echo $job->getHowToApply() ?></p> <div class="meta"> <small>posted on <?php echo $job->getCreatedAt('m/d/Y') ?></small> </div> <div style="padding: 20px 0"> <a href="<?php echo url_for('job/edit?id='.$job->getId()) ?>">Edit</a> </div> </div>
Ce template utilise la variable $job passée en paramètre par l'action servant à afficher les informations sur un job. Comme nous avons renommé la variable utilisée dans le template ($job au lieu de $projet01_job), vous devez également modifier le nom de la variable envoyée au template depuis l'action show (attention, elle s'y trouve deux fois). Remplacez le contenu par :
<?php /** * job actions. * * @package projet01 * @subpackage job * @author Your name here * @version SVN: $Id: actions.class.php 12474 2008-10-31 10:41:27Z fabien $ */ class jobActions extends sfActions { public function executeIndex(sfWebRequest $request) { $this->projet01_job_list = Projet01JobPeer::doSelect(new Criteria()); } public function executeShow(sfWebRequest $request) { $this->job = Projet01JobPeer::retrieveByPk($request->getParameter('id')); $this->forward404Unless($this->job); } public function executeNew(sfWebRequest $request) { $this->form = new Projet01JobForm(); } public function executeCreate(sfWebRequest $request) { $this->forward404Unless($request->isMethod('post')); $this->form = new Projet01JobForm(); $this->processForm($request, $this->form); $this->setTemplate('new'); } public function executeEdit(sfWebRequest $request) { $this->forward404Unless($projet01_job = Projet01JobPeer::retrieveByPk($request->getParameter('id')), sprintf('Object projet01_job does not exist (%s).', $request->getParameter('id'))); $this->form = new Projet01JobForm($projet01_job); } public function executeUpdate(sfWebRequest $request) { $this->forward404Unless($request->isMethod('post') || $request->isMethod('put')); $this->forward404Unless($projet01_job = Projet01JobPeer::retrieveByPk($request->getParameter('id')), sprintf('Object projet01_job does not exist (%s).', $request->getParameter('id'))); $this->form = new Projet01JobForm($projet01_job); $this->processForm($request, $this->form); $this->setTemplate('edit'); } public function executeDelete(sfWebRequest $request) { $request->checkCSRFProtection(); $this->forward404Unless($projet01_job = Projet01JobPeer::retrieveByPk($request->getParameter('id')), sprintf('Object projet01_job does not exist (%s).', $request->getParameter('id'))); $projet01_job->delete(); $this->redirect('job/index'); } protected function processForm(sfWebRequest $request, sfForm $form) { $form->bind($request->getParameter($form->getName()), $request->getFiles($form->getName())); if ($form->isValid()) { $projet01_job = $form->save(); $this->redirect('job/edit?id='.$projet01_job->getId()); } } }
Remarquez que certains accesseurs Propel prennent des paramètres. Comme nous avons défini la colonne created_at de type timestamp, l'accesseur getCreatedAt() prend en paramètre le format de la date à renvoyer :
$job->getCreatedAt('m/d/Y');
La description d'un job utilise le helper simple_format_text() afin de formater le texte en HTML, en remplaçant notamment les retours chariots par des balises <br />. Comme ce helper fait parti du groupe Text et que celui-ci n'est pas chargé par défaut, nous le chargeons manuellement en utilisant le helper use_helper().
Contributeur(e)s : L'ami René (L'auteur), YoBoY.